Async-Await ≈ Generators + Promises
在这篇文章中,我将阐述 ES2017 的异步函数(async functions)本质上是如何在两个较旧的 JavaScript 特性之间“博弈”的:即生成器(generators)和 Promise。原文称这两者都是在 ES2016 规范中更早加入该语言的。
开始阅读之前 ..
- 本文并非关于 Promise、生成器或异步函数的入门教程。
- 本文的唯一目标是阐述如何利用 Promise 和生成器来实现异步函数。
- 文中不会对异步函数与其他方案孰优孰劣发表任何观点。
- 文中使用的代码示例是为了便于解释而精心构思(甚至略显刻意)的,切勿用于任何严肃的生产环境。
但是,为什么呢……?
既然现在原生环境已经支持异步函数了,那我们还有必要去深究它们的工作原理吗?
嗯,除了满足好奇心这个显而易见的理由外,还有一个重要原因是为了兼容旧平台。如果你希望使用了这些新特性的代码能在旧版浏览器或旧版 Node.js 上运行,你就必须借助像 Babel 这样的工具,将这些新特性“转译”为旧特性。
因此,当你需要阅读或调试这些经过转译的代码时,如果深刻理解异步函数是如何被拆解为生成器和 Promise 的,将会派上大用场。举个例子,下面是一个简单的异步函数:
// 一个异步函数 (ES2017)
async function foo() {
await bar();
}
这个函数会被 Babel 转换为以下 ES2016 代码(暂时不必纠结于理解它,我们稍后会详细讲解):
//Transformed by Babel to ES2016.
let foo = (() => {
var _ref = _asyncToGenerator(function*() {
yield bar();
});
return function foo() {
return _ref.apply(this, arguments);
};
})();
function _asyncToGenerator(fn) {
return function() {
var gen = fn.apply(this, arguments);
return new Promise(function(resolve, reject) {
function step(key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
function(value) {
step("next", value);
},
function(err) {
step("throw", err);
}
);
}
}
return step("next");
});
};
}
它们看起来差别很大!然而,如果你理解了异步函数(async functions)的实际工作原理,这种转换就显得相当直观了。
另一个有趣的事实是,浏览器也是以类似的方式实现异步函数的,也就是说,它们将异步代码转换为使用生成器(generators)和 Promise 的形式,这与 Babel 的转换方式非常相似。
好的,那么这是怎么发生的呢?
有时候,为了理解某样东西是如何运作的,最好的方法就是自己动手去构建它。所以让我们换个角度思考:
想象一下,我们得到了一段使用异步函数(async functions)的代码,我们要如何仅用 Promise 和生成器函数(generator functions)来重写它呢?
下面是async函数:
// An async function.
async function init() {
// Perform asynchronous task1 and logs the result.
const res1 = await doTask1();
console.log(res1);
// Perform asynchronous task2 using the result of task1.
// Then log the result.
const res2 = await doTask2(res1);
console.log(res2);
// Perform asynchronous task3 using the result of task2.
// Then log the result.
const res3 = await doTask3(res2);
console.log(res3);
// Finally return the result of task3.
return res3;
}
它依次执行三个异步任务,其中每个任务都依赖于前一个任务的完成。最后,它返回最后一个任务的结果。
我们如何使用生成器(generators)来编写它?
生成器是一种可以退出并在稍后重新进入的函数。让我们快速回顾一下它们是如何工作的。以下是一个简单的生成器函数:
// A simple generator function.
function* gen() {
const a = yield 1;
const b = yield true;
const c = yield "foo";
console.log(a, b, c);
}
这个生成器函数
gen 有一些有趣的方面(摘自 MDN 文档):- 当调用一个生成器函数时,它的函数体并不会立即执行。相反,它会返回一个遵循迭代器协议(iterator protocol)的迭代器对象,即该对象拥有一个
next方法。 - 执行
gen函数体的唯一方法是调用其迭代器对象上的next方法。每次调用next方法时,函数体会一直执行到遇到下一个yield表达式。该表达式的值会被从迭代器中返回。 - 这个
next方法还接受一个参数。带参数调用它时,会将当前的yield表达式替换为该参数,并恢复执行直到遇到下一个yield表达式。
为了阐明(非常、非常粗略地来说)..
- 生成器函数是逐个
yield执行的(即一次一个yield表达式),由其迭代器(next方法)来驱动。 - 每一个
yield都有一种“给出 → 暂停 → 接收”的行为,可以这么说。- 它将当前
yield表达式的值给出给迭代器。 - 然后在这一点暂停,直到迭代器的
next方法再次被调用。 - 当
next方法再次被调用时,它从方法中接收参数,并将当前暂停的yield表达式替换为该参数。然后它移动到下一个yield。
- 它将当前
你可能想要再次阅读上面的总结,或者参考极其棒的 MDN 文档!
但这对我们有什么帮助呢?
到现在你可能会想,生成器函数是如何解决我们的问题的呢?我们需要建模一个异步流程,必须等待某些任务完成后才能继续向前执行。但在我们目前的讨论中,一切都是同步的。我们要如何做到这一点呢?
- 嗯,这里最重要的洞察是,生成器函数也可以
yieldPromise 对象。
生成器函数可以产出一个 Promise(例如一个异步任务),并且可以通过控制其迭代器来暂停执行,直到这个 Promise 解决(resolve)或拒绝(reject),然后再使用解决后的值(或拒绝的值)继续执行。这种将迭代器与产出的 Promise 结合在一起的模式,使我们能够像这样来建模我们的需求:
// An implementation using a generator-function.
function* init() {
// Yields a promise for task1.
const res1 = yield doTask1();
// Logs the result of the yield expression.
console.log(res1);
// Yields a promise for task2.
const res2 = yield doTask2(res1);
// Logs the result of the yield expression.
console.log(res2);
// Yields a promise for task3.
const res3 = yield doTask3(res2);
// Logs the result of the yield expression.
console.log(res3);
// Finally return it.
return res3;
}
(注意这个生成器函数和我们的异步函数很像!)
但这只是故事的一半。现在我们需要一种方法来执行它的函数体。我们需要一个函数,能够控制这个生成器函数的迭代器,使其在每次yield一个 Promise 时暂停,并在 Promise 解决(resolve)或拒绝(reject)后继续执行。这听起来可能很复杂,但其实非常容易实现,如下所示:
// This function takes a generator function
// & executes its body to completion.
function runner(genFn) {
// Iterator for the generator function.
const itr = genFn();
// This function is called recursively once
// the current promise is resolved.
function run(arg) {
const result = itr.next(arg);
if (result.done) {
// Return the current value if iteration is finished.
return result.value;
} else {
// Execute the `run` function, only when the current promise resolves.
return Promise.resolve(result.value).then(run);
}
}
return run();
}
一个用于执行生成器函数的函数。(仅作解释说明,请勿使用!)
现在,我们可以使用这个runner函数来执行我们的生成器函数
init,如下所示:// A generator-function for our requirement (same as before)
function* init() {
const res1 = yield doTask1();
console.log(res1);
const res2 = yield doTask2(res1);
console.log(res2);
const res3 = yield doTask3(res2);
console.log(res3);
return res3;
}
// Use the `runner` function to execute `init`.
runner(init);
就这样!这种
runner函数与generator函数的组合,实现了与原始异步函数(async function)类似的执行效果。请注意,这个运行器函数仅用于演示概念。它并不适合任何严肃的生产环境使用。如果您正在寻找一个经过妥善实现的版本,您可以在这里找到它。
总结一下:
使用async-await进行完整实现,如下所示:
// 模拟异步任务1:返回一个Promise,模拟异步操作(如接口请求、文件读取)
function doTask1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('✅ 执行任务1完成');
resolve('任务1结果:数据A'); // 异步成功后返回结果
}, 1000); // 模拟1秒延迟
});
}
// 模拟异步任务2:依赖任务1的结果
function doTask2(prevResult) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('✅ 执行任务2完成,依赖:', prevResult);
resolve('任务2结果:数据B(基于' + prevResult + ')');
}, 1000);
});
}
// 模拟异步任务3:依赖任务2的结果
function doTask3(prevResult) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('✅ 执行任务3完成,依赖:', prevResult);
resolve('任务3结果:数据C(基于' + prevResult + ')');
}, 1000);
});
}
// 主异步函数
async function init() {
try {
// 执行异步任务1并打印结果
const res1 = await doTask1();
console.log('📝 任务1输出:', res1);
// 执行异步任务2(依赖任务1结果)并打印结果
const res2 = await doTask2(res1);
console.log('📝 任务2输出:', res2);
// 执行异步任务3(依赖任务2结果)并打印结果
const res3 = await doTask3(res2);
console.log('📝 任务3输出:', res3);
// 最终返回任务3结果
console.log('\n🎉 所有任务执行完成!最终结果:', res3);
return res3;
} catch (error) {
// 统一捕获异步过程中的错误
console.error('❌ 执行失败:', error);
throw error; // 抛出错误方便外部捕获
}
}
// 调用执行主函数
init();
运行结果如下:

使用Generators + Promises重写async+await
// 模拟异步任务1:返回一个Promise,模拟异步操作(如接口请求、文件读取)
function doTask1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('✅ 执行任务1完成');
resolve('任务1结果:数据A'); // 异步成功后返回结果
}, 1000); // 模拟1秒延迟
});
}
// 模拟异步任务2:依赖任务1的结果
function doTask2(prevResult) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('✅ 执行任务2完成,依赖:', prevResult);
resolve('任务2结果:数据B(基于' + prevResult + ')');
}, 1000);
});
}
// 模拟异步任务3:依赖任务2的结果
function doTask3(prevResult) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('✅ 执行任务3完成,依赖:', prevResult);
resolve('任务3结果:数据C(基于' + prevResult + ')');
}, 1000);
});
}
// This function takes a generator function
// & executes its body to completion.
function runner(genFn) {
// Iterator for the generator function.
const itr = genFn();
// 此处仅仅是run函数的定义
// return run()是启动器,启动了run函数及其内部的迭代
function run(arg) {
const result = itr.next(arg);
// 下面两行为测试代码
console.log("arg is:",arg)
console.log("result is:",result)
if (result.done) {
// Return the current value if iteration is finished.
return result.value;
} else {
// Execute the `run` function, only when the current promise resolves.
// 这一行是理解整个函数运行的关键点
return Promise.resolve(result.value).then(run);
}
}
// 没有 return run(),整个异步流程永远不会开始!
return run();
}
// A generator-function for our requirement (same as before)
function* init() {
const res1 = yield doTask1();
console.log('📝 任务1输出:', res1);
const res2 = yield doTask2(res1);
console.log('📝 任务2输出:', res2);
const res3 = yield doTask3(res2);
console.log('📝 任务3输出:', res3);
console.log('\n🎉 所有任务执行完成!最终结果:', res3);
return res3;
}
// Use the `runner` function to execute `init`.
runner(init);
运行结果如下:

我们先是用 async 函数开了个头,随后又利用生成器(generators)和 Promise 写了一个一模一样的实现。也就是说,上面这两段代码的效果是类似的!
📚 进阶练习
- 在本文开头,我们了解了 Babel 如何利用生成器(generators)和 Promise 将
async代码转换为 ES2016 代码。现在你可以回头看看那段转换后的代码,对比一下我们的runner函数与_asyncToGenerator函数有多么相似。事实上,那个_asyncToGenerator函数就是我们这个极简版runner函数的“万无一失”的健壮版本。 - 如果你依然兴致盎然,可以再往前迈一步,也就是将
async函数转换为 ES2015 代码——也就是在不使用生成器的情况下实现。为此,你将不得不模拟生成器本身(例如使用带有switch分支的状态化死循环,可以参考 regenerator 项目)。
我希望这个解释能揭开
async 函数背后的神秘面纱。它们提供了更简洁的语法,从而减少了代码中的“噪音”。
浙公网安备 33010602011771号