学习链接
异步迭代和 generator(Symbol.asyncIterator
和 for await ... of
)
异步操作-async
异步的角度来看,async 算是 generator 的语法糖。
async
函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器
Generator 函数的执行需要调用
next
方法,或者用co
模块,也就是需要依靠执行器,才能真正执行,得到最后结果。async
函数自带执行器。也就是说,async
函数的执行,与普通函数一模一样,只要一行。
(2)更好的语义
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象。而
async
函数的await
命令后面,可以是 Promise 对象和原始类型的值
(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)
(4)返回值是 Promise
async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。可以用then
方法指定下一步的操作。进一步说,
async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
语法
// 继发
let foo = await getFoo();
let bar = await getBar();
// 执行完 getFoo() 才会执行 getBar()
// 同时触发写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 同时触发写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
并发执行、继发执行
function dbFuc(db) { //这里不需要 async
let docs = [{}, {}, {}];
// 可能得到错误结果
docs.forEach(async function (doc) {
await db.post(doc);
});
}
// 上面代码可能不会正常工作
// 原因是这时三个db.post()操作将是并发执行,也就是同时执行,而不是继发执行
// 改为 for 循环
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
// 用数组的 reduce() 方法
async function dbFuc(db) {
let docs = [{}, {}, {}];
await docs.reduce(async (_, doc) => {
await _;
// 等待 上一次 值为 undefined 状态为 fulfilled 的对象
await db.post(doc); // 异步操作
// return undefined
}, undefined);
}
可保留运行堆栈
const a = () => {
b().then(() => c());
};
上面代码中,函数 a
内部运行了一个异步任务 b()
。
当 b()
运行的时候,函数 a()
不会中断,而是继续执行。
等到 b()
运行结束,可能 a()
早就运行结束了,
b()
所在的上下文环境已经消失了。
如果 b()
或 c()
报错,错误堆栈将不包括 a()
。
const a = async () => {
await b();
c();
};
上面代码中,b()
运行的时候,a()
是暂停执行,上下文环境都保存着。一旦 b()
或 c()
报错,错误堆栈将包括 a()
。
async 实现原理
将 Generator 函数和自动执行器包装在同一个函数中。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
function spawn(genF) {
return new Promise((resolve, reject) => {
const gen = genF();
step(() => gen.next(undefined));
function step(nextF) {
let next;
try {
next = nextF();
} catch (e) {
return reject(e);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(
v => { step(() => gen.next(v)); },
e => { step(() => gen.throw(e)); }
);
}
});
}