参考网站:
async 原理:
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () { //spawn函数就是自动执行器
return new Promise(function(resolve, reject) {
const gen = genF();
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(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
});
}
Generator 函数介绍:
async 使用:
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句(await语句整体可以看为一个同步执行区域)。
下面是一个例子。
async function getStockPriceByName(name) {
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
console.log('111');// 要等stockPrice 执行完才会执行
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});
由于async
函数返回的是 Promise 对象,可以作为await
命令的参数。
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
async的执行顺序
async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
async的返回
async
函数返回一个 Promise 对象。
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
async的报错
async
函数内部抛出错误,会导致返回的 Promise 对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到。
await
命令
await
命令后面跟随的对象
await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function f() {
// 等同于
// return 123;
return await 123; //等同于return 123
}
f().then(v => console.log(v))
// 123
另一种情况是,await
命令后面是一个thenable
对象(即定义then
方法的对象),那么await
会将其等同于 Promise 对象。
class Sleep {
constructor(timeout) {
this.timeout = timeout;
}
then(resolve, reject) { // 因为定义了then方法,await会将其视为Promise处理。
const startTime = Date.now();
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
);
}
}
(async () => {
const actualTime = await new Sleep(1000);
console.log(actualTime);
})();
await返回
await
命令后面的 Promise 对象如果变为reject
状态,则reject
的参数会被catch
方法的回调函数接收到。任何一个await
语句后面的 Promise 对象变为reject
状态,那么整个async
函数都会中断执行。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
如果await
后面的异步操作出错(throw new Error()),那么等同于async
函数返回的 Promise 对象被reject
。
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了
两种解决await中断的方法
1放在try...catch模块中
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
2await
后面的 Promise 对象再跟一个catch
方法
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出错了
// hello world
await注意点
- 前面已经说过,
await
命令后面的Promise
对象,运行结果可能是rejected
,所以最好把await
命令放在try...catch
代码块中。async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一种写法 async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }); }
-
多个
await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。let foo = await getFoo(); let bar = await getBar();
上面代码中,
getFoo
和getBar
是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo
完成以后,才会执行getBar
,完全可以让它们同时触发。// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); // 这里已经触发函数, let barPromise = getBar(); //getFoo和getBar都是同时触发,这样就会缩短程序的执行时间 let foo = await fooPromise; let bar = await barPromise;
-
await
命令只能用在async
函数之中,如果用在普通函数,就会报错。如果将
forEach
方法的参数改成async
函数,也有问题。function dbFuc(db) { //这里不需要 async let docs = [{}, {}, {}]; // 可能得到错误结果 docs.forEach(async function (doc) { //原因是三个db.post操作将是并发执行,也就是同时执行,而不是继发执行 await db.post(doc); }); }
上面代码可能不会正常工作。正确的写法是采用
for
循环。async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }
如果确实希望多个请求并发执行,可以使用
Promise.all
方法。当三个请求都会resolved
时,下面两种写法效果相同。async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); // 注意map里的函数是return db.post(doc) let results = await Promise.all(promises); console.log(results); } // 或者使用下面的写法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }
-
async 函数可以用于保留运行堆栈。
const a = () => { b().then(() => c()); };
上面代码中,函数
a
内部运行了一个异步任务b()
。当b()
运行的时候,函数a()
不会中断,而是继续执行。等到b()
运行结束,可能a()
早就运行结束了,b()
所在的上下文环境已经消失了。如果b()
或c()
报错,错误堆栈将不包括a()
。现在将这个例子改成
async
函数。const a = async () => { await b(); c(); };
上面代码中,
b()
运行的时候,a()
是暂停执行,上下文环境都保存着。一旦b()
或c()
报错,错误堆栈将包括a()
。
async
函数对 Generator 函数的改进
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co
模块,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();
上面的代码调用了asyncReadFile
函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next
方法,或者用co
模块,才能真正执行,得到最后结果。
(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
命令的语法糖。
async 函数与 Promise、Generator 函数的比较。
1与Promise 比较
语义更加明确
2与Generator 比较
有一个任务运行器spawn,它返回一个 Promise 对象,而且必须保证yield
语句后面的表达式,必须返回一个 Promise。
扩展部分
asyncIterator异步遍历器
异步遍历器的最大的语法特点,就是调用遍历器的next
方法,返回的是一个 Promise 对象。
asyncIterator
.next()
.then(
({ value, done }) => /* ... */
);
上面代码中,asyncIterator
是一个异步遍历器,调用next
方法以后,返回一个 Promise 对象。因此,可以使用then
方法指定,这个 Promise 对象的状态变为resolve
以后的回调函数。回调函数的参数,则是一个具有value
和done
两个属性的对象,这个跟同步遍历器是一样的。
for await...of遍历同步的 Iterator 接口
for...of
循环用于遍历同步的 Iterator 接口。新引入的for await...of
循环,则是用于遍历异步的 Iterator 接口。
async function f() {
for await (const x of createAsyncIterable(['a', 'b'])) {
console.log(x);
}
}
// a
// b
上面代码中,createAsyncIterable()
返回一个拥有异步遍历器接口的对象,for...of
循环自动调用这个对象的异步遍历器的next
方法,会得到一个 Promise 对象。await
用来处理这个 Promise 对象,一旦resolve
,就把得到的值(x
)传入for...of
的循环体。
异步 Generator 函数
就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。
在语法上,异步 Generator 函数就是async
函数与 Generator 函数的结合。
async function* gen() {
yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }
上面代码中,gen
是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用next
方法,返回一个 Promise 对象。
yield* 语句
yield*
语句也可以跟一个异步遍历器。
async function* gen1() {
yield 'a';
yield 'b';
return 2;
}
async function* gen2() {
// result 最终会等于 2
const result = yield* gen1();
}
上面代码中,gen2
函数里面的result
变量,最后的值是2
。
与同步 Generator 函数一样,for await...of
循环会展开yield*(自动调用next())
。
(async function () {
for await (const x of gen2()) {
console.log(x);
}
})();
// a
// b