作为iOS的研发人员,转向鸿蒙平台,学习ArkTS时,对于在函数前面加async的理解一直是懵懵的。一直是当作可异步执行的函数。
但是,对于调用异步函数时,前面加 await的这种写法,该函数也必须是async就不太理解了。
参考多方的资料和代码实践,将个人的理解进行了整理。为了更好地理解,下面先在实现、执行、返回值、异常处理及调用等层面进行对比:
核心区别速查表
| 特性 | 普通函数 (无 async) | async 函数 |
|---|---|---|
| 实现约束 | 不能使用 await 关键字 | 可以使用 await 关键字来暂停执行 |
| 执行流程 | 同步执行:从第一行执行到最后一行,不会中途暂停 | 异步执行:遇到 await 会暂停,将线程控制权交还,直到等待的 Promise 解决 |
| 返回值 | 直接返回一个明确的值(如 number, string, object 等) | 总是返回一个 Promise 对象 |
| 异常处理 | 使用 try...catch 捕获同步错误 | 使用 try...catch 捕获同步错误和 await 表达的 Promise 的拒绝(rejection) |
| 调用方式 | 直接使用变量接收返回值:const result = func() | 必须使用 .then()/.catch() 或 在另一个 async 函数中用 await 调用:const result = await func() |
详细解释与代码示例
1. 返回值不同:Value vs. Promise
-
普通函数:返回的就是
return语句指定的值。// 函数定义 function normalFunction() { return 58; // 直接返回数字 58 } // 函数调用,result的值是58 (类型是 number) const result = normalFunction(); -
async函数:无论你返回什么,它都会用一个 Promise 对象把你返回的值“包裹”起来。// 函数定义 async function asyncFunction() { // 等价于 return Promise.resolve(58); return 58; } // 函数调用,result的值是一个Promise类型(感兴趣的可以调试一下,还没找到方法通过log输出Promise实例) const result = asyncFunction(); // 之后通过result获取返回值: result.then((value) => { // value: 58 }); // 上述代码也可以写成,这是不是就眼熟了 asyncFunction().then((value) => { // value: 58 }); // 或者在另一个通过await 以同步的方式执行该异步函数 // value 58 const value = await asyncFunction();
2. 执行流程不同:同步 vs. 可暂停的异步
-
普通函数:执行是同步且连续的。函数体内的代码会一口气执行完毕,期间不会被打断(除非有异常)。它会阻塞后面代码的执行,直到自己完全跑完。
function normalFunction() { console.log('第一步'); console.log('第二步'); // 紧接着执行 } console.log('开始'); normalFunction(); console.log('结束'); // log输出: // "开始" // "第一步" // "第二步" // "结束" -
async函数:执行是异步的。虽然它也会立即执行,但一旦遇到await关键字,它会暂停自己的执行,交出线程控制权,让事件循环(Event Loop)去执行其他代码(比如处理UI点击、网络响应等)。它会一直等到await后面的 Promise 变成“已解决”(fulfilled)或“已拒绝”(rejected)状态后,才恢复执行。// fetchData() 是一个返回 Promise 的函数,1秒后获取到数据 async function fetchData() { const promise: Promise<number> = new Promise((resolve: Function, reject: Function) => { setTimeout(() => { resolve(88); }, 1000); }) return promise; } async function asyncFunction() { console.log('第一步'); let result = await fetchData(); console.log(""+result+""); // 88 console.log('第二步'); // 恢复执行 } console.log('开始'); asyncFunction().then(()=>{ console.log('执行结束'); }); console.log('调用结束'); // log输出: // "第一步" // "调用结束" // "第二步" // "88" // "执行结束"这种“暂停-恢复”的特性使得用同步代码的书写方式来处理异步操作成为可能,代码更清晰,避免了“回调地狱”(Callback Hell)。
3. 错误处理
-
普通函数:使用
try...catch只能捕获同步错误。function normalFunction() { throw new Error('同步错误!'); } try { normalFunction(); } catch (error) { console.error('捕获到错误:', error); // 这里能捕获到 } -
async函数:使用try...catch可以捕获同步错误和由await导致的 Promise 拒绝(rejection)。// asyncReject() 模拟返回一个被 reject 的 Promise async function asyncReject() { const promise: Promise<number> = new Promise((resolve: Function, reject: Function) => { setTimeout(() => { reject(new Error('错误!')); }, 1000); }) return promise; } async function asyncFunction() { try { const data = await asyncReject(); // 当Promise 被 reject,会被 catch 住! } catch (error) { console.error('失败:', error); // 既能捕获 asyncReject 的失败,也能捕获函数体内异常 } } asyncFunction();如果你不用
try...catch,也可以在调用时使用.catch()来捕获错误:async function asyncFunction_notry() { const data = await asyncReject(); // 当Promise 被 reject,上层可捕获 } asyncFunction_notry().then(()=>{ console.error("then"); }).catch(()=>{ console.error("catch"); }).finally(()=>{ console.error("finally"); })
总结与如何选择
| 普通函数 | async 函数 | |
|---|---|---|
| 使用场景 | 执行简单的、同步的计算和操作。 | 执行需要等待的操作,如: 1. 网络请求 (fetch, axios) 2. 文件读写 3. 定时器 (setTimeout 包装成 Promise) 4. 任何返回 Promise 的API |
| 关键决策点 | 你的操作是立即完成的吗? | 你的操作需要等待一段时间(如I/O)吗? |
| 返回值 | 值 | Promise实例 |
总结来说: 加 async 的函数和普通函数最根本的区别在于返回值类型和内部是否允许使用 await。这导致了它们在执行流程和控制方式上的差异。当需要在函数内部进行异步操作(尤其是需要等待 Promise 的结果)时,就使用 async 函数并结合 await。否则,使用普通函数即可。