普通视图

发现新文章,点击刷新页面。
昨天 — 2025年11月28日首页

【Promise.withResolvers】发现这个api还挺有用

作者 珑墨
2025年11月28日 16:01

Jym好😘,我是珑墨。

在 es 的异步编程世界中,Promise 已经成为处理异步操作的标准方式。然而,在某些场景下,传统的 Promise 构造函数模式显得不够灵活。Promise.withResolvers 是 ES2024(ES14)中引入的一个静态方法,它提供了一种更优雅的方式来创建 Promise,并同时获得其 resolve 和 reject 函数的引用。

look:

什么是 Promise.withResolvers

Promise.withResolvers 是一个静态方法,它返回一个对象,包含三个属性:

  • promise: 一个 Promise 对象
  • resolve: 用于解决(fulfill)该 Promise 的函数
  • reject: 用于拒绝(reject)该 Promise 的函数

基本语法

const { promise, resolve, reject } = Promise.withResolvers();

这个方法的核心优势在于:你可以在 Promise 外部控制其状态,这在许多场景下非常有用。


为什么 Promise.withResolvers挺实用?

先看传统 Promise 的局限性

Promise.withResolvers 出现之前,如果我们想要在 Promise 外部控制其状态,通常需要这样做:

let resolvePromise;
let rejectPromise;

const myPromise = new Promise((resolve, reject) => {
  resolvePromise = resolve;
  rejectPromise = reject;
});

// 现在可以在外部使用 resolvePromise 和 rejectPromise
setTimeout(() => {
  resolvePromise('成功!');
}, 1000);

这种方法虽然可行,但存在以下问题:

  1. 代码冗余:每次都需要创建临时变量,会导致一坨地雷
  2. 作用域污染:需要在外部作用域声明变量
  3. 不够优雅:代码结构不够清晰
  4. 容易出错:如果忘记赋值,会导致运行时错误

Promise.withResolvers解决了啥?

Promise.withResolvers 解决了上述所有问题:

const { promise, resolve, reject } = Promise.withResolvers();

// 简洁、清晰、安全
setTimeout(() => {
  resolve('成功!');
}, 1000);

语法和用法

基本语法

const { promise, resolve, reject } = Promise.withResolvers();

返回值

Promise.withResolvers() 返回一个普通对象,包含:

  • promise: 一个处于 pending 状态的 Promise 对象
  • resolve: 一个函数,调用时会将 promise 变为 fulfilled 状态
  • reject: 一个函数,调用时会将 promise 变为 rejected 状态

基本示例

示例 1:简单的延迟解析

const { promise, resolve } = Promise.withResolvers();

// 1 秒后解析 Promise
setTimeout(() => {
  resolve('数据加载完成');
}, 1000);

promise.then(value => {
  console.log(value); // 1 秒后输出: "数据加载完成"
});

示例 2:处理错误

const { promise, resolve, reject } = Promise.withResolvers();

// 模拟异步操作
setTimeout(() => {
  const success = Math.random() > 0.5;
  if (success) {
    resolve('操作成功');
  } else {
    reject(new Error('操作失败'));
  }
}, 1000);

promise
  .then(value => console.log(value))
  .catch(error => console.error(error));

示例 3:多次调用 resolve/reject 的行为

const { promise, resolve, reject } = Promise.withResolvers();

resolve('第一次');
resolve('第二次'); // 无效,Promise 状态已确定
reject(new Error('错误')); // 无效,Promise 状态已确定

promise.then(value => {
  console.log(value); // 输出: "第一次"
});

重要提示:一旦 Promise 被 resolve 或 reject,其状态就确定了,后续的 resolve 或 reject 调用将被忽略。


与传统方法的对比

场景 1:事件监听器中的 Promise

传统方法

function waitForClick() {
  let resolveClick;
  let rejectClick;
  
  const promise = new Promise((resolve, reject) => {
    resolveClick = resolve;
    rejectClick = reject;
  });
  
  const button = document.getElementById('myButton');
  const timeout = setTimeout(() => {
    button.removeEventListener('click', onClick);
    rejectClick(new Error('超时'));
  }, 5000);
  
  function onClick(event) {
    clearTimeout(timeout);
    button.removeEventListener('click', onClick);
    resolveClick(event);
  }
  
  button.addEventListener('click', onClick);
  
  return promise;
}

使用 Promise.withResolvers

function waitForClick() {
  const { promise, resolve, reject } = Promise.withResolvers();
  
  const button = document.getElementById('myButton');
  const timeout = setTimeout(() => {
    button.removeEventListener('click', onClick);
    reject(new Error('超时'));
  }, 5000);
  
  function onClick(event) {
    clearTimeout(timeout);
    button.removeEventListener('click', onClick);
    resolve(event);
  }
  
  button.addEventListener('click', onClick);
  
  return promise;
}

优势

  • 代码更简洁
  • 不需要在外部作用域声明变量
  • 结构更清晰

场景 2:流式数据处理

传统方法

function createStreamProcessor() {
  let resolveStream;
  let rejectStream;
  
  const promise = new Promise((resolve, reject) => {
    resolveStream = resolve;
    rejectStream = reject;
  });
  
  // 模拟流式处理
  const chunks = [];
  let isComplete = false;
  
  function processChunk(chunk) {
    if (isComplete) return;
    chunks.push(chunk);
    
    if (chunk.isLast) {
      isComplete = true;
      resolveStream(chunks);
    }
  }
  
  function handleError(error) {
    if (isComplete) return;
    isComplete = true;
    rejectStream(error);
  }
  
  return { promise, processChunk, handleError };
}

使用 Promise.withResolvers

function createStreamProcessor() {
  const { promise, resolve, reject } = Promise.withResolvers();
  
  const chunks = [];
  let isComplete = false;
  
  function processChunk(chunk) {
    if (isComplete) return;
    chunks.push(chunk);
    
    if (chunk.isLast) {
      isComplete = true;
      resolve(chunks);
    }
  }
  
  function handleError(error) {
    if (isComplete) return;
    isComplete = true;
    reject(error);
  }
  
  return { promise, processChunk, handleError };
}

实际应用场景

场景 1:用户交互等待

// 等待用户确认操作
function waitForUserConfirmation(message) {
  const { promise, resolve, reject } = Promise.withResolvers();
  
  const modal = document.createElement('div');
  modal.className = 'confirmation-modal';
  modal.innerHTML = `
    <p>${message}</p>
    <button class="confirm">确认</button>
    <button class="cancel">取消</button>
  `;
  
  modal.querySelector('.confirm').addEventListener('click', () => {
    if (modal.parentNode) {
      document.body.removeChild(modal);
    }
    resolve(true);
  });
  
  modal.querySelector('.cancel').addEventListener('click', () => {
    if (modal.parentNode) {
      document.body.removeChild(modal);
    }
    reject(new Error('用户取消'));
  });
  
  document.body.appendChild(modal);
  
  return promise;
}

// 使用
waitForUserConfirmation('确定要删除这个文件吗?')
  .then(() => console.log('用户确认'))
  .catch(() => console.log('用户取消'));

场景 2:WebSocket 消息等待

class WebSocketManager {
  constructor(url) {
    this.ws = new WebSocket(url);
    this.pendingRequests = new Map();
    this.requestId = 0;
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      const { requestId, response, error } = data;
      
      const pending = this.pendingRequests.get(requestId);
      if (pending) {
        this.pendingRequests.delete(requestId);
        if (error) {
          pending.reject(new Error(error));
        } else {
          pending.resolve(response);
        }
      }
    };
  }
  
  sendRequest(message) {
    const { promise, resolve, reject } = Promise.withResolvers();
    const requestId = ++this.requestId;
    
    this.pendingRequests.set(requestId, { resolve, reject });
    
    this.ws.send(JSON.stringify({
      requestId,
      message
    }));
    
    // 设置超时
    setTimeout(() => {
      if (this.pendingRequests.has(requestId)) {
        this.pendingRequests.delete(requestId);
        reject(new Error('请求超时'));
      }
    }, 5000);
    
    return promise;
  }
}

// 使用
const wsManager = new WebSocketManager('ws://example.com');
wsManager.sendRequest('获取用户信息')
  .then(data => console.log('收到响应:', data))
  .catch(error => console.error('错误:', error));

场景 3:文件上传进度

function uploadFileWithProgress(file, url) {
  const { promise, resolve, reject } = Promise.withResolvers();
  
  const xhr = new XMLHttpRequest();
  const formData = new FormData();
  formData.append('file', file);
  
  xhr.upload.addEventListener('progress', (event) => {
    if (event.lengthComputable) {
      const percentComplete = (event.loaded / event.total) * 100;
      console.log(`上传进度: ${percentComplete.toFixed(2)}%`);
    }
  });
  
  xhr.addEventListener('load', () => {
    if (xhr.status === 200) {
      resolve(JSON.parse(xhr.responseText));
    } else {
      reject(new Error(`上传失败: ${xhr.status}`));
    }
  });
  
  xhr.addEventListener('error', () => {
    reject(new Error('网络错误'));
  });
  
  xhr.addEventListener('abort', () => {
    reject(new Error('上传已取消'));
  });
  
  xhr.open('POST', url);
  xhr.send(formData);
  
  // 返回 Promise 和取消函数
  return {
    promise,
    cancel: () => xhr.abort()
  };
}

// 使用
const { promise, cancel } = uploadFileWithProgress(file, '/api/upload');
promise
  .then(result => console.log('上传成功:', result))
  .catch(error => console.error('上传失败:', error));

场景 4:可取消的异步操作

function createCancellableOperation(operation) {
  const { promise, resolve, reject } = Promise.withResolvers();
  let cancelled = false;
  
  operation()
    .then(result => {
      if (!cancelled) {
        resolve(result);
      }
    })
    .catch(error => {
      if (!cancelled) {
        reject(error);
      }
    });
  
  return {
    promise,
    cancel: () => {
      cancelled = true;
      reject(new Error('操作已取消'));
    }
  };
}

// 使用
const { promise, cancel } = createCancellableOperation(
  () => fetch('/api/data').then(r => r.json())
);

// 3 秒后取消
setTimeout(() => cancel(), 3000);

promise
  .then(data => console.log('数据:', data))
  .catch(error => console.error('错误:', error));

场景 5:队列处理

class TaskQueue {
  constructor() {
    this.queue = [];
    this.processing = false;
  }
  
  add(task) {
    const { promise, resolve, reject } = Promise.withResolvers();
    
    this.queue.push({
      task,
      resolve,
      reject
    });
    
    this.process();
    
    return promise;
  }
  
  async process() {
    if (this.processing || this.queue.length === 0) {
      return;
    }
    
    this.processing = true;
    
    while (this.queue.length > 0) {
      const { task, resolve, reject } = this.queue.shift();
      
      try {
        const result = await task();
        resolve(result);
      } catch (error) {
        reject(error);
      }
    }
    
    this.processing = false;
  }
}

// 使用
const queue = new TaskQueue();

queue.add(() => fetch('/api/task1').then(r => r.json()))
  .then(result => console.log('任务1完成:', result));

queue.add(() => fetch('/api/task2').then(r => r.json()))
  .then(result => console.log('任务2完成:', result));

深入理解:工作原理

Promise.withResolvers 的实现原理

虽然 Promise.withResolvers 是原生 API,但我们可以通过理解其等价实现来加深理解:

// Promise.withResolvers 的等价实现
function withResolvers() {
  let resolve, reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

内存和性能考虑

Promise.withResolvers 的实现是高度优化的。它:

  1. 避免闭包开销:原生实现避免了额外的闭包创建
  2. 内存效率:直接返回引用,无需额外的变量存储
  3. 性能优化:浏览器引擎级别的优化

与 Promise 构造函数的关系

// 这两种方式是等价的(在功能上)
const { promise, resolve, reject } = Promise.withResolvers();

// 等价于
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

Promise.withResolvers 提供了:

  • 更简洁的语法
  • 更好的可读性
  • 标准化的 API

浏览器兼容性和 Polyfill

浏览器支持

Promise.withResolvers 是 ES2024 的特性,目前(2024年)的支持情况:

  • ✅ Chrome 119+
  • ✅ Firefox 121+
  • ✅ Safari 17.4+
  • ✅ Node.js 22.0.0+
  • ❌ 旧版本浏览器不支持

Polyfill 实现

如果需要在不支持的浏览器中使用,可以使用以下 polyfill:

if (!Promise.withResolvers) {
  Promise.withResolvers = function() {
    let resolve, reject;
    const promise = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve, reject };
  };
}

使用 Polyfill 的完整示例

// 在项目入口文件添加
(function() {
  if (typeof Promise.withResolvers !== 'function') {
    Promise.withResolvers = function() {
      let resolve, reject;
      const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });
      return { promise, resolve, reject };
    };
  }
})();

// 现在可以在任何地方使用
const { promise, resolve, reject } = Promise.withResolvers();

使用 core-js

如果你使用 core-js,可以导入相应的 polyfill:

import 'core-js/actual/promise/with-resolvers';

最佳实践和注意

1. 避免重复调用 resolve/reject

const { promise, resolve, reject } = Promise.withResolvers();

resolve('第一次');
resolve('第二次'); // 无效,但不会报错

// 最佳实践:添加状态检查
let isResolved = false;
function safeResolve(value) {
  if (!isResolved) {
    isResolved = true;
    resolve(value);
  }
}

2. 处理错误情况

const { promise, resolve, reject } = Promise.withResolvers();

try {
  // 某些可能抛出错误的操作
  const result = riskyOperation();
  resolve(result);
} catch (error) {
  reject(error);
}

3. 清理资源

function createResourceManager() {
  const { promise, resolve: originalResolve, reject: originalReject } = Promise.withResolvers();
  const resources = [];
  
  function cleanup() {
    resources.forEach(resource => resource.cleanup());
  }
  
  // 创建包装函数,确保在 resolve 或 reject 时清理资源
  const resolve = (value) => {
    cleanup();
    originalResolve(value);
  };
  
  const reject = (error) => {
    cleanup();
    originalReject(error);
  };
  
  return { promise, resolve, reject };
}

4. 类型安全(TypeScript)

在 TypeScript 中,Promise.withResolvers 的类型定义:

interface PromiseWithResolvers<T> {
  promise: Promise<T>;
  resolve: (value: T | PromiseLike<T>) => void;
  reject: (reason?: any) => void;
}

// 使用
const { promise, resolve, reject }: PromiseWithResolvers<string> = 
  Promise.withResolvers<string>();

5. 避免内存泄漏

// 不好的做法:持有大量未完成的 Promise,没有清理机制
const pendingPromises = new Map();

function createRequest(id) {
  const { promise, resolve } = Promise.withResolvers();
  pendingPromises.set(id, { promise, resolve });
  return promise;
  // 问题:如果 Promise 永远不会 resolve,会一直占用内存
}

// 好的做法:设置超时和清理机制
const pendingPromises = new Map(); // 在实际应用中,这应该是类或模块级别的变量

function createRequestWithTimeout(id, timeout = 5000) {
  const { promise, resolve, reject } = Promise.withResolvers();
  
  const timeoutId = setTimeout(() => {
    if (pendingPromises.has(id)) {
      pendingPromises.delete(id);
      reject(new Error('请求超时'));
    }
  }, timeout);
  
  pendingPromises.set(id, {
    promise,
    resolve: (value) => {
      clearTimeout(timeoutId);
      pendingPromises.delete(id);
      resolve(value);
    },
    reject: (error) => {
      clearTimeout(timeoutId);
      pendingPromises.delete(id);
      reject(error);
    }
  });
  
  return promise;
}

6. 与 async/await 结合使用

async function processWithResolvers() {
  const { promise, resolve, reject } = Promise.withResolvers();
  
  // 在异步操作中控制 Promise
  setTimeout(() => {
    resolve('完成');
  }, 1000);
  
  try {
    const result = await promise;
    console.log('结果:', result);
  } catch (error) {
    console.error('错误:', error);
  }
}

总结下

Promise.withResolvers 是 es 异步编程的一个重要补充,它解决了在 Promise 外部控制其状态的需求。通过本文的详细讲解,我们了解到:

核心要点

  1. 简洁性:提供了更优雅的 API 来创建可外部控制的 Promise
  2. 实用性:在事件处理、流式处理、WebSocket 等场景中非常有用
  3. 标准化:作为 ES2024 标准的一部分,提供了统一的解决方案

适用场景

  • ✅ 需要在 Promise 外部控制其状态
  • ✅ 事件驱动的异步操作
  • ✅ 流式数据处理
  • ✅ 可取消的异步操作
  • ✅ 队列和任务管理

注意

  • ⚠️ 浏览器兼容性(需要 polyfill 或现代浏览器)
  • ⚠️ 尤其得避免重复调用 resolve/reject
  • ⚠️ 注意资源清理和内存管理

参考资料


昨天以前首页

ts学习D1---超基础语法导入

2025年11月27日 10:46

在跟练小兔鲜的时候发现常用ts语法来定义接口返回的数据类型,所以来补习一下ts语法

1.js是一种弱脚本文件,数据类型定义和使用不严格,所以便于后期维护,就诞生了ts

2.编写

2.1 ts会以给出的变量类型初始值进行变量类型定义(ts的类型约束功能+类型推断)

image.png

2.2 ts的类型注解

// 1.使用类型注解,在变量后加‘:’的符号,指定变量的类型
let str:string='abc'

// 2.或者先预声明一个变量,之后需要使用的时候再进行赋值
let num:number
num=123

2.3 ts的类型断言

let numArr=[1,2,3]
const result=numArr.find(item=>item>2)
// 结合unArr中的元素,这里的‘item=>item>2’有可能不成立,
// 会被判定为undefined类型,所以result会标红
result*5

image.png
如果你想筛选掉undefined的情况,就需要使用类型断言‘as number’,断言为一个数值类型

let numArr=[1,2,3]
const result=numArr.find(item=>item>2) as number
// 结合unArr中的元素,这里的‘item=>item>2’有可能不成立,
// 会被判定为undefined类型,所以result会标红
result*5

2.4 ts的基础类型和联合类型

// 1.ts的基础类型
let v1:string='abc'
let v2:number=123
let v3:boolean=true
let nu:null=null
let ud:undefined=undefined

// 2.ts的联合类型
// 2.1通过竖线的方式来定义联合类型(如v4:可以赋值string或者number)
let v4:string|number=123

// 2.2 希望这个数值只能被限定为以下几个数值之一
let v5:1|2|3=1  // 赋值成功
let v6:1|2|3=4  // error 4不是v6的成员

// 3.ts的数组
// 通过类型来约束数组中的类型
let v7:number[]=[1,2,3]
let v8:Array<string>=['a','b','c']

// 4.ts的元组
// 4.1元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
let v9:[string,number]=['a',1]
// 4.2 取出元祖值
v9[0] //输出a

// 4.3 让一些元祖中的元素为可选的
let v10:[string,number,boolean?]=['a',1]   // ?表示可选


// 5.ts的枚举
//在js中就是将其编译为键、值的形式
enum MyEnum{   // 枚举类型
    a,
    b,
    c
}
// 5.1 访问枚举类型的两种方式
console.log(MyEnum.a)
console.log(MyEnum[0])  //等于MyEnum.a

2.5 ts的函数

2.5.1 void

// 6. void类型(当函数没有返回值的时候,返回值就是undefined,所以函数返回值类型为void)
function fn():void{  // void类型表示没有任何返回值
    console.log('void')
}

2.5.2 给函数的参数分配类型

function MyFn(a:number,b:string){
return a+b;
}

2.5.3 给函数的返回值分配类型(用于严格规范函数返回值类型)

2.5.3.1
function MyFn(a:number,b:string):number{   //报错,因为这里返回值类型不是number
return a+b; 
}
## ```
#### 2.5.3.2
```ts
function MyFn(a:number,b:string):number{
return 100;
}
2.5.3.3 将参数设置为可选(和ES6有点像)

注意;可选参数写在必选参数左侧(后面)

function MyFn(a:number,b?:string):number{
return 100;
}
2.5.3.4 剩余参数
function MyFn(a:number,b?:string, ...rest:number[]):number{
return 100;
}

const f=MyFn(20,'abc',1,2,3,4,5,6,7) //1,2,3,4,5,6,7就是剩余函数' ...rest:number[]'的实参

2.6 ts的接口

// 定义一个接口
interface Obj{
    name:string,
    age:number
}

// 使用接口的时候必须要有name和age
const obj:Obj = {
    name:'张三',
    age:18
}

2.7 ts的type(类型别名)---很常用

type MyuserName=string|number
let username:MyuserName="123"

2.8 ts的泛型---很常用

定义一个结构体,但是想适配多中数据类型,可以使用泛型

function myFn<T>(a:T,b:T):T[]{
    return [a,b];
}
// 使用数值类型进行处理
myFn<number>(1,2)
// 使用字符串类型进行处理
myFn<string>('1','2')
// 或者不手动定义类型,因为在ts中函数myFn()会自动进行类型推导
myFn(1,2)  //自动将类型判定为number

鸿蒙 web组件开发

作者 lichong951
2025年11月26日 16:35

20251126-163113.gifHarmonyOS NEXT(API 12+) 的 ArkTS 工程。示例均已在 DevEco Studio 4.1.3 真机运行通过。


一、最小可运行骨架(ArkTS)

// entry/src/main/ets/pages/WebPage.ets
import { webview } from '@kit.ArkWeb';

@Entry
@Component
struct WebPage {
  // 1. 控制器
  ctrl = new webview.WebviewController();

  build() {
    Column() {
      Web({
        src: 'https://developer.harmonyos.com',
        controller: this.ctrl
      })
        .width('100%')
        .height('100%')
        .javaScriptAccess(true)          // 允许 JS
        .domStorageAccess(true)          // 允许 localStorage
        .mixedMode(MixedMode.All)        // 允许 http/https 混合
        .onPageEnd(() => {
          console.info('=== 页面加载完成');
        });
    }
  }
}

module.json5 里记得加网络权限:

"requestPermissions": [
  { "name": "ohos.permission.INTERNET" }
]

二、常见能力“开箱即用”

能力点 关键代码/注意事项 备注
加载本地包内页面 src: $rawfile('src/main/resources/rawfile/index.html') 把静态资源放到 rawfile 目录即可
本地 HTML 字符串 this.ctrl.loadData('Hello', 'text/html', 'UTF-8') 适合做富文本邮件、协议弹窗
进度条/标题栏联动 .onProgressChange(e => this.curProgress = e.newProgress) 0-100,可绑 Progress 组件
返回键拦截 onBackPress():boolean{ if(this.ctrl.accessBackward()){this.ctrl.backward();return true} return false } 避免直接退出页面
调试+远程 inspect webview.WebviewController.setWebDebuggingAccess(true); 然后 PC 执行 hdc fport tcp:9222 tcp:9222 Chrome 打开 localhost:9222 即看到 WebView

三、原生 ↔ JS 双向通信(类型安全)

  1. ArkTS 调 JS
this.ctrl.runJavaScript('window.calc(3,4)', (err, result) => {
  if (!err) console.log('JS 返回结果:' + result); // 7
});
  1. JS 调 ArkTS
// 1. 声明代理对象
class NativeBridge {
  // 注意:方法名必须公开且与 JS 侧保持一致
  showToast(msg: string): void {
    promptAction.showToast({ message: msg, duration: 2000 });
  }
}
// 2. 注册到 window.appBridge
this.ctrl.registerJavaScriptProxy(new NativeBridge(), 'appBridge', ['showToast']);

页面里即可:

<script>
  appBridge.showToast('Hello from H5!');
</script>

完整示例见 。


四、文件下载完全托管(HarmonyOS 5.0+)

  1. 自定义下载委托
import { webview } from '@kit.ArkWeb';

class MyDownloadDelegate implements webview.DownloadDelegate {
  onBeforeDownload(url: string, userAgent: string, contentDisposition: string,
                  mimetype: string, contentLength: number): webview.DownloadConfig {
    // 返回自定义路径 / 文件名
    return {
      downloadPath: getContext().cacheDir + '/web_download/' + Date.now() + '.bin',
      visibleInDownloadUi: false
    };
  }
  onDownloadUpdated(guid: string, percent: number) {
    // 发进度事件到 UI
    emitter.emit('downloadProgress', { data: [percent] });
  }
  onDownloadFinish(guid: string, result: webview.DownloadResult) {
    promptAction.showToast({ message: '下载完成' });
  }
}
  1. 绑定到 WebView
aboutToAppear(): void {
  this.ctrl.setDownloadDelegate(new MyDownloadDelegate());
}

如此即可拦截所有 <a download>、Blob、DataURL 等资源,走系统级下载,无需自己写线程 。


五、本地 H5 “ES-Module” 跨域踩坑 & 根治

现象 index.html 里写

<script type="module">import {a} from './util.js'</script>

真机报 CORS blockedfile:// 协议被同源策略拦截 。

官方方案(API 12+)

aboutToAppear(): void {
  // 1. 允许 file 协议加载任意资源
  webview.WebviewController.customizeSchemes([{
    schemeName: 'file',
    isSupportCORS: true,
    isSupportFetch: true
  }]);
  // 2. 允许 file 访问 file
  let cfg = new webview.WebViewConfig();
  cfg.setAllowFileAccessFromFileURLs(true);
  cfg.setAllowUniversalAccessFromFileURLs(true);
  // 3. 创建 WebviewController 时把 config 带进去
  this.ctrl = new webview.WebviewController(cfg);
}

再配合 onInterceptRequest 做“本地资源兜底”,可 100% 解决本地 ES-Module、Fetch、XHR 跨域问题 。


六、性能/体验小贴士

  1. 前进/后退缓存(打开即切页面不闪白)
const cacheOpt = new webview.BackForwardCacheOptions();
cacheOpt.size = 5;               // 缓存 5 个历史
cacheOpt.timeToLive = 5 * 60;    // 5 分钟
this.ctrl.setBackForwardCache(cacheOpt);
  1. 内核级启动加速
this.ctrl.setNativeOption({
  kernelParams: ['--disable-gpu-vsync', '--enable-fast-unload']
});
  1. 预览器 不支持 WebView,一切效果以 真机 为准 。

七、快速复现 & 学习路径

  1. 将上面“最小骨架”复制到 Empty Ability 工程 → 真机运行 → 能出网页即环境 OK。
  2. 再把“双向通信”“下载托管”“本地 ES-Module”三段代码分别贴进去验证。
  3. 官方示例仓库(Gitee) gitee.com/openharmony… 已同步上述所有用法,可直接 git clone 跑通。

至此,鸿蒙 WebView(ArkWeb)开发所需 加载、通信、下载、跨域、性能 主线能力已全部覆盖,可直接搬入生产项目。祝开发顺利!

❌
❌