javaScript实现 async 函数
创始人
2025-06-01 04:03:05

挑战介绍

本节我们来挑战一道大厂面试真题 —— 实现 async 函数。

挑战内容
请实现一个 myAsync 函数,这个函数用来模拟 async 函数的功能,最终能通过下面的测试代码即可:

function fn() {return myAsync(function* () {yield 1;yield 2;return 3;});
}const p = fn();
p.then((val) => {console.log(val); // 打印 3
});

提示
async 函数其实就是 Generator 函数的语法糖,它的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里,就是本题要实现的 myAsync 函数。

这个 myAsync 函数接收一个 Generator 回调函数作为参数,在 myAsync 函数中执行 Generator 函数并自动执行 next 方法,最终返回一个 Promise 实例,把状态为 done 的值 resolve 出来,把错误的信息 reject 出来。

知识点

Generator 函数

Generator 函数是 ES6 提供的一种异步编程解决方案,它是可以用来控制迭代器的函数,并且语法与传统的函数完全不同,我们来看下面这个示例:

function printNum() {console.log(1);console.log(2);console.log(3);
}printNum(); // 程序最终依次输出 1,2,3

这是一个正常的函数,如果我们把这个函数改造成 Generator 函数,代码如下:

function* printNum() {yield console.log(1);yield console.log(2);yield console.log(3);
}printNum(); // 这样执行不会有任何反应

此时执行 printNum,不会有任何反应,加上了 yield 关键字后,程序中的打印逻辑都被中断了。

我们需要调用函数返回值的 next 方法,才会生效,代码如下:

function* printNum() {yield console.log(1);yield console.log(2);yield console.log(3);
}fn = printNum();
fn.next(); // 打印 1
fn.next(); // 打印 2
fn.next(); // 打印 3

这样,程序的执行就会变得可控,它们可以暂停,然后在需要的时候恢复,小结一下:

  1. Generator 函数比普通函数多一个 *。
  2. 函数内部用 yield 来控制暂停代码的执行。
  3. 函数的返回值通过调用 next 来恢复代码的执行。

协程

传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做"协程"(coroutine),意思是多个线程互相协作,完成异步任务。

协程有点像函数,又有点像线程。它的运行流程大致如下:

第一步,协程 A 开始执行。

第二步,协程 A 执行到一半,进入暂停,执行权转移到协程 B。

第三步,(一段时间后)协程 B 交还执行权。

第四步,协程 A 恢复执行。

上面流程的协程 A,就是异步任务,因为它分成两段(或多段)执行。

举例来说,读取文件的协程写法如下。

我们以实际的代码来举例:

function* A() {console.log("A");yield B(); // 暂停 A,执行 Bconsole.log("end");
}
function B() {console.log("B");return 1; // B 执行完了,返回,继续执行 A
}
let gen = A();
gen.next();
gen.next();// A
// B
// end

上面代码的函数 A 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield 命令是异步两个阶段的分界线。

协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除 yield 命令,简直一模一样。

async 函数的实现原理
我们知道了 Generator 函数的用法,现在用它来处理异步,代码如下:

const fs = require("fs");const readFile = function (fileName) {return new Promise(function (resolve, reject) {fs.readFile(fileName, function (error, data) {if (error) return reject(error);resolve(data);});});
};const genFn = function* () {const a = yield readFile("a.json");const b = yield readFile("b.json");console.log(JSON.parse(a));console.log(JSON.parse(b));
};

上面代码的函数 genFn 可以写成 async 函数,就是下面这样。

const asyncReadFile = async function () {const a = await readFile("a.json");const b = await readFile("b.json");console.log(JSON.parse(a));console.log(JSON.parse(b));
};

一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。

但是 Generator 函数的执行,每一步都要执行 next 方法,非常不方便,能不能让它一次性执行完毕呢?

上文中的 genFn 方法,我们让他执行完,代码如下:

let g = genFn();
// next返回值中有一个 value 值,这个 value 是 yield 后面的结果
g.next().value((err, data1) => {g.next(data1).value((err, data2) => {g.next(data2);});
});

注意这里的 value 值,是调用 next 方法生成的,比如:

function* printNum() {yield 1;yield 2;return 3;
}fn = printNum();
console.log(fn.next()); // {value: 1, done: false}
console.log(fn.next()); // {value: 2, done: false}
console.log(fn.next()); // {value: 3, done: true}

当调用 next 方法时,返回一个对象,它的 value 属性就是当前 yield 表达式的值,done 属性的值表示遍历是否结束。

上文的 genFn 方法中,我们只执行了两个异步操作,万一异步操作多起来,又会陷入回调地狱了,我们把这里的逻辑封装一下:

function step(nextFn) {const next = (err, data) => {let res = nextFn.next(data);// 如果 res.done 为 true,才表示迭代结束,返回if (res.done) return;// 否则执行递归的逻辑res.value(next);};next();
}
step(genFn());

这里有一个递归的过程,我们把这一步封装称为自动执行 Generator 函数。

而 async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args) {// ...
}// 等同于function fn(args) {return myAsync(function* () {// ...});
}

我们介绍了这么多,终于回到了本题的 myAsync 函数,本题我们要实现 async 的功能,就需要返回一个Promise 实例,把 Generator 函数中状态为 done 的值 resolve 出来,把错误的信息 reject 出来,最终代码实现如下:

function myAsync(genFn) {// 返回一个 Promise 实例return new Promise(function (resolve, reject) {const gen = genFn();// 自动执行器的封装,里面是递归的逻辑function step(nextFn) {let next;//try {next = nextFn();} catch (e) {return reject(e);}// 如果已经到 done 状态了,就 resolve 最终的值if (next.done) {return resolve(next.value);}// 不是 done 状态,说明程序还没执行完,就继续递归Promise.resolve(next.value).then(function (v) {step(function () {return gen.next(v);});},function (e) {// 错误的逻辑 reject 出来step(function () {return gen.throw(e);});});}step(function () {return gen.next();});});
}

这样我们就实现了 myAsync 函数,但实现这个函数并不是重点,重点是学习 Generator 函数的用法以及理解 async 是如何通过 Generator 函数来实现的。

相关内容

热门资讯

合作37年后彩星玩具与“忍者龟... 彩星玩具对“忍者龟”的特许权协议自2027年起将不获续期。12月23日,香港玩具企业彩星玩具(008...
“亨通光电”即将腾飞? 周末梳理电力产业链,以及提到了各环节的一些公司,有一家公司是这两天问我最多的,那就是亨通光电,这还让...
“并不是我们辞退了人家”,长城... 对于魏牌不断换“帅”一事,长城汽车董事长魏建军首次公开回应。12月22日,魏建军在与媒体交流时针对魏...
美元创八年来最差年度表现!专家... 2025年,美元经历了一场“滑铁卢”。衡量美元对一篮子主要货币汇率的美元指数,在年内暴跌约9%,正迈...
8连板胜通能源:公司不涉及机器... 新京报贝壳财经讯 12月23日,胜通能源公告,公司股票自12月12日以来连续8个交易日涨停,累计涨幅...