前情提要 :本文将从js
的异步发展历程引出async/await
的浅层认知:async/await
是对promise
的封装。在进一步讲解async/await
是如何实现对promise
的封装。
js异步编程场景,学过前端的同学,对此并不陌生。在前端知识体系中,对js异步的认识尤为重要。而编写异步代码,首要解决的难题就是异步事件的同步逻辑编写。因为往往在异步事件结束后,往往会有对异步事件结果的处理需求,这就需要我们能够掌握异步事件的结束时机。一个不可控的异步,无异于是项目里面的一个不定时炸弹。
如今大众熟悉的异步事件有:promise
、async/await
。而这两者的又有什么关系,promise
之前的异步又是什么样子的呢?
JS异步的发展历史
回调函数
早期的Javascript
中,主要通过回调函数 来处理异步操作 。在完成异步任务后 ,会调用一个回调函数来处理结果。
javascript
// 模拟异步读取文件的函数
function readFileAsync(filePath, callback) {
setTimeout(() => {
// 模拟异步读取文件的过程
const fileContent = "这是文件的内容";
// 调用回调函数,并将读取到的文件内容作为参数传递给回调函数
callback(null, fileContent);
}, 1000); // 模拟1秒后异步操作完成
}
// 调用异步函数,并传入回调函数来处理异步操作的结果
readFileAsync('example.txt', (error, content) => {
if (error) {
console.error('读取文件失败:', error);
} else {
console.log('文件内容:', content);
}
});
一个回调函数是不是看着还好,那如果回调里面还有回调呢,这简直是个灾难-----回调地狱 。这时候,我们的救星-promise
出现了
Promise
Promise 对象表示一个异步操作的最终完成或失败,并且可以链式调用 ,减少 了回调地狱的问题。
scss
getData().then(processData).then(render).catch(handleError);
但是 在处理多个异步操作时,仍然需要嵌套多个 Promise
,代码结构仍然复杂 ,有没有更好的解决方案呢?我们今天的主角:Async/await
就出现了。
Async/Await
async/await
是 ES2017
中新增的语法糖。基于 Promise
,并进一步简化了异步代码的编写 。async
函数用来声明异步函数,内部可以使用await
关键字来等待 Promise
对象的状态变化,使得异步代码的写法更加类似于同步代码,可读性更高。
scss
async function fetchData() {
try {
const data = await getData();
const result = await processData(data);
await render(result);
// 后续操作...
} catch (error) {
handleError(error);
}
}
Promise 和 async/await 的联系
Promise
是异步操作的基础,通过链式调用.then()
和.catch()
处理异步任务的状态和结果,较为底层。async/await
是基于Promise
的语法糖,更直观易懂,使得异步代码的编写更加简洁清晰,避免了回调地狱。async/await
实际上是对Promise
的封装,内部实现也是基于Promise
的状态和结果。
async/await底层实现原理剖析
async/await
要想封装promise
实现代码调用顺序的同步,必须要实现代码执行顺序的控制,为此就需要借助生成器(generator)。其实async/await
看起来,像极了generator(生成器)
,只是生成器它不能自动迭代,只能手动触发。ECMAScript 6关于gennerators
的介绍如下图:
要理解下面所说的
async/await
底层实现原理,就必须要理解什么是生产器,及生成器是如何使用的(这里不过多讲解)
babeljs转化代码
下面我们通过babeljs,看一下async/await
是如何借助生成器实现promise
的封装。
着重看babel转化后的
ES5
代码。
代码解读
_regeneratorRuntime
:生成器(generator
)相关实现。asyncGeneratorStep
:驱动Generator的调度器 ,用来执行next()
、throw()
等操作。_asyncToGenerator
:把一个generator function
转换成async function
,并返回一个 Promise。func = /*#__PURE__*/function () {xxx}
:封装的核心逻辑
借助Generator实现封装
异步代码:
dart
const func = async () => {
const a = await get()
return a
}
对应的转义代码:
javascript
var func = /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
var a;
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return get();
case 2:
a = _context.sent;
return _context.abrupt("return", a);
case 4:
case "end":
return _context.stop();
}
}, _callee);
}));
return function func() {
return _ref.apply(this, arguments);
};
}();
对这段代码是不是看得云里雾里的?
1、_context
是什么?
_context
是一个对象,regeneratorRuntime
生成器状态机的核心,它会根据 next
指针的值,依次执行对应的代码块,负责存储当前生成器的执行状态,包括:
prev
:上一条执行的指令编号。next
:下一条要执行的指令编号。sent
:存储yield
语句的返回值。done
:表示生成器是否完成。
_context.abrupt(type, value)
:用于提前终止生成器函数的执行,并返回一个值。
常见 abrupt
的类型
_context.abrupt(type, value) |
作用 |
---|---|
"return", value |
立即返回 value 并终止执行 |
"throw", error |
抛出错误 error |
"break", label |
跳出循环(通常不直接使用) |
"continue", label |
继续执行(通常不直接使用) |
regeneratorRuntime.wrap(innerFn, outerFn, self, tryLocsList)
:将一个普通的Generator
函数封装成符合 regenerator
运行时的标准 Generator
对象。
innerFn
:原始的 Generator 函数(即function*
)。outerFn
:外部 Generator 函数的构造器(如果有的话)。self
:执行Generator
的this
上下文。tryLocsList
:存储try/catch/finally
代码块的位置。
2、_context.prev = _context.next
作用是什么?
记录当前执行的位置,以便于在下一次 yield
或 await
之后,知道上一次执行到哪一步,便于恢复状态。
在 JavaScript 运行时,async/await
被转译为生成器(generator
),需要 prev
记录当前执行的 next
值,以便:
- 在
await
发生时,保存执行上下文。 - 发生异常时,能跳转到正确的错误处理位置。
- 通过
next()
方法继续执行,恢复到正确的case。
prev
笔者认为可以理解为操作系统里面,系统检测到中断时,保存当前执行程序的PC指针,以便中断处理结束后回到原程序继续执行。
3、switch
这样书写case
对比的变量是哪个?
switch ((_context.prev = _context.next))
这里的 case
语句是 对比 _context.next
的值
4、执行顺序梳理
到这里就可以知道程序执行顺序了,每次func()
开始,最先执行case 0
,遇到异步操作,修改 _context.next
的值,return
异步操作,等待异步操作完成,在下一个微任务队列中继续执行 case 2
,知道程序结束,进入case "end"
,调用_context.stop()
,将生成器的done
置为true
,从而结束整个 Generator
执行流程。
5、类比
只有一个异步操作看懂了,那么多个呢?
6、 其它关键代码,生成器自迭代实现
asyncGeneratorStep
:
scss
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg); // arg是_context.sent的值
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
_asyncToGenerator
:
javascript
function _asyncToGenerator(fn) {
return function () {
var self = this, args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);// 关键,触发case 0代码的执行,迭代的开始
});
};
}