阅读 85

JavaScript 异步函数解析

前言

在学习 JavaScript 的过程中,理解并灵活运用异步相关知识是一件不容易的事情,这体现在代码可读性、健壮性上,好在 ES6 出现后挽回了这一局面,我们不再需要编写可读性不高的回调嵌套,也不用为了代码的健壮性而处处小心,这得益于 Promiseasync Function,它们给我们带来了更优秀的异步方案,今天我们就来学习异步函数相关知识。

学习异步函数前,要求读者熟悉 Promise 与 生成器,如果未了解过,可以看看读者的文章

ES6 新特性详解 - Promise
ES6 新特性详解 - 迭代器与生成器

async Function

虽然 Promise 对于传统的异步编程而言已经足够优秀,但精益求精的前辈们仍觉不足,Promise 的代码执行顺序对于开发者而言依然不够直观,为次,ES6 又提出了异步函数的相关概念。

异步函数,即一个特殊函数,通过在函数声明前添加 async 关键字标识

async function () {} // or const asyncArrow = async () => {} 复制代码

在异步函数中,能够使用关键词 await,它期待一个 Promise 值,并等待它决议,此时会暂停当前的程序并返回,控制权交还给主线程去执行其他任务

async function asyncFun() {   await Promise.resolve();   console.log('end'); } asyncFun(); console.log('start'); 复制代码

上述代码中的正确执行顺序是 startend,这意味着函数调用后异步函数内的程序被暂停了,所以首先输出 start,然后输出 end,它的行为如下

  1. 调用异步函数

  2. 等待一个立即完成的 Promise

  3. 给立即完成的 Promise 添加一个微任务

  4. 返回

  5. 输出 start

  6. 微任务执行,asyncFun 程序恢复执行

await 虽然期待一个 Promise 值,但这不是必须的,它可以等待任何值,此时浏览器会将这个值包装为一个 Promise

async function asyncFun() {   await 1;   // 类似于   await Promise.resolve(1); } 复制代码

await 还能够获取到 Promise 的完成值

async function asyncFun() {   const result = await Promise.resolve('fulfilled');   console.log(result); } asyncFun(); // fulfilled 复制代码

如果等待的 Promise 被拒绝,那么就会在程序停止的位置抛出异常,我们能够通过 try...catch 进行异常捕获

async function asyncFun() {   try {     await Promise.reject('出错了');   } catch(err) {     console.log(err); // 出错了   } } asyncFun(); 复制代码

异步函数的返回值会被包装成一个 Promise,如果没有显示的返回一个值,那么 Promise 完成值就是隐式返回的 undefined

async function asyncFun() {   return 'complete'; } asyncFun()   .then((value) => {     console.log(value); // complete   }) 复制代码

看到这里,其实异步函数的一些特性就讲完了,但是你有没有发现异步函数与生成器有很多相像的地方呢?

// async async function asyncFun() {   await Promise.resolve(); } // generator function *generator() {   yield Promise.resolve(); } 复制代码

异步函数通过 async 来标识,而生成器通过在 function 关键字后添加 * 来标识,且它们都有各自的关键字能用于暂停程序的执行,但仅仅如此吗?看一下下面的代码

function *generator() {   const result = yield Promise.resolve('generator');   console.log(result); // generator } function run(exec) {   // 假设 exec 必定是一个生成器   // 获取生成器对象   const g = exec();   // 启动生成器,获取 yield、return 出的值   const p = g.next();   // 假设 p.value 必定是一个 Promise   p.value.then(     value => g.next(value),     err => g.throw(err)   ); } run(generator); 复制代码

上述代码中,我们添加了一个 run 函数,用于控制生成器的执行,run 函数利用 yield 双向数据传递的特性,接受生成器返回的值,这里我们假定为 Promise 值,并给它添加一个 then 处理回调,当这个 Promise 完成时在成功回调中调用生成器对象的 next 方法并将完成值注入到上一个导致程序暂停的 yield 身上,如果失败则调用 throw 方法并注入失败原因。

现在你发现了吗?其实异步函数只是一个语法糖,基于 Promise + 生成器我们也能实现完全相同的功能,所缺的只是一个控制生成器执行流程的执行器而已。

实现执行器

我们已经了解了异步函数其实只是 Promise + 生成器的语法糖,但还缺少了一个关键的执行器函数,以下是摘抄自 《你不知道的JavaScript》 的实现

function run(exec, ...args) {   // 调用生成器函数并传递参数   const it = exec.apply(this, args);   return Promise.resolve()     .then(       function handleNext(value) { // 不断处理 next 调用         const next = it.next(value); // 获取 next 返回值         // 立即执行函数,传递 next 返回值         return (function handleResult(next) {           if (next.done) { // 执行完毕直接返回             return next.value;           } else {             // 未执行完毕则给 yield 出的 Promise 添加then处理回调             // 同时避免 yield 非 Promise 值,需要进行一层 resolve 包装             return Promise.resolve(next.value)               .then(                 handleNext, // 成功回调,继续调用 handleNext 获取下一个值                 function handleErr(err) { // 失败回调                   return Promise.resolve(                     it.throw(err) // 注入异常,如果异常在生成器内部被处理则继续调用 handleResult                   ).then(handleResult);                 }               )           }         })(next);       }     ) } 复制代码

拥有了这个执行器函数,我们就能够基于 Promise + 生成器来模拟异步函数的行为了

function *generator() {   try {     const result = yield Promise.reject('出错啦');   } catch(err) {     console.log(err); // 出错啦   }   const data = yield Promise.resolve('data');   console.log(data);   return 'complete'; } run(generator)   .then(value => {     console.log(value); // complete   }) // 出错啦 // data // complete 复制代码

结语

本文讲述了异步函数的相关概念,并了解了异步函数其实只是语法糖,我们完全能够自己实现相同的功能,可以看到,生成器在这中间充当了非常重要的角色,以后可能还会基于生成器出现更多强大的模式。


作者:yuanyxh
链接:https://juejin.cn/post/7171307888248356894
来源:https://www.77cxw.com/


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐