出现原因
Promise 太麻烦了
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
p.then((x) => console.log(x)); // 3
// 改造下
async function foo() {
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
console.log(await p);
}
foo();
可以结合生成器 最后一个例子 https://www.yuque.com/yejielin/mypn47/um6a15#kFdYK
=================
async
声明
1、普通 执行流程(同步)
2、有返回值的 执行流程
1、return 不是promise对象
返回值一定是Promise,会被包裹到Promise.resolve( )中,因此可以直接接.then(res),res就是return 的结果(没有就是undefined)
2、return 实现then的对象
return 的是一个对象,并且实现了then方法,这样就会直接执行这个then方法,然后把结果传递到后面的then
3、return Promise对象
如果我们的异步函数的返回值是Promise 对象,那么后面Promise的状态会由这个Promise对象决定,是执行then还是执行catch
3、抛出异常的 执行流程
普通函数,运行到 throw 抛出异常,就会报错并终止整个程序继续运行;
而async 异步函数,运行到 throw 抛出异常,不会报错,不会终止整个程序继续运行,而是会作为Promise的reject来传递,后面可以用(必须要用)catch来捕获异常
=================
await
1、后面跟 成功的Promise
也可以直接写new Promise
2、后面跟普通的值
3、后面跟实现then的对象
4、后面跟失败的Promise
会直接跳出运行,把结果返回给外面(且本异步函数后面不执行了),需要用catch接
也可以通过try catch语句捕获
限制
// 不允许:await 出现在了箭头函数中
function foo() {
const syncFn = () => {
return await Promise.resolve('foo');
};
console.log(syncFn());
}
// 不允许:await 出现在了同步函数表达式中
function baz() {
const syncFn = function() {
return await Promise.resolve('baz');
};
console.log(syncFn());
}
停止和恢复执行
JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。
等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。
async function foo() {
console.log(await Promise.resolve('foo'));
}
async function bar() {
console.log(await 'bar');
}
async function baz() {
console.log('baz');
}
foo();
bar();
baz();
// baz
// bar
// foo
async function foo() {
console.log(2);
console.log(await Promise.resolve(8));
console.log(9);
}
async function bar() {
console.log(4);
console.log(await 6);
console.log(7);
}
console.log(1);
foo();
console.log(3);
bar();
console.log(5);
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
(1) 打印 1;
(2) 调用异步函数 foo();
(3)(在 foo()中)打印 2;
(4)(在 foo()中)await 关键字暂停执行,向消息队列中添加一个期约在落定之后执行的任务;
(5) 期约立即落定,把给 await 提供值的任务添加到消息队列;
(6) foo()退出;
(7) 打印 3;
(8) 调用异步函数 bar();
(9)(在 bar()中)打印 4;
(10)(在 bar()中)await 关键字暂停执行,为立即可用的值 6 向消息队列中添加一个任务;
(11) bar()退出;
(12) 打印 5;
(13) 顶级线程执行完毕;
(14) JavaScript 运行时从消息队列中取出解决 await 期约的处理程序,并将解决的值 8 提供给它;
(15) JavaScript 运行时向消息队列中添加一个恢复执行 foo()函数的任务;
(16) JavaScript 运行时从消息队列中取出恢复执行 bar()的任务及值 6;
(17)(在 bar()中)恢复执行,await 取得值 6;
(18)(在 bar()中)打印 6;
(19)(在 bar()中)打印 7;
(20) bar()返回;
(21) 异步任务完成,JavaScript 从消息队列中取出恢复执行 foo()的任务及值 8;
(22)(在 foo()中)打印 8;
(23)(在 foo()中)打印 9;
(24) foo()返回。
顺序执行、并行执行
// 定义一个延迟执行
async function randomDelay(id) {
// 延迟 0~1000 毫秒
const delay = Math.random() * 1000;
return new Promise((resolve) => setTimeout(() => {
console.log(`${id} finished`);
resolve();
}, delay));
}
// 顺序执行
async function foo() {
const t0 = Date.now();
await randomDelay(0);
await randomDelay(1);
await randomDelay(2);
await randomDelay(3);
await randomDelay(4);
console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
// 0 finished
// 1 finished
// 2 finished
// 3 finished
// 4 finished
// 877ms elapsed
// 并行执行
async function foo() {
const t0 = Date.now();
const promises = Array(5).fill(null).map((item, index) => randomDelay(index));
for (const p of promises) {
console.log(`awaited ${await p}`);
}
/*相当于
const p0 = randomDelay(0);
const p1 = randomDelay(1);
const p2 = randomDelay(2);
const p3 = randomDelay(3);
const p4 = randomDelay(4);
await p0;
await p1;
await p2;
await p3;
await p4;
*/
console.log(`${Date.now() - t0}ms elapsed`);
}
foo();
// 1 finished
// 2 finished
// 4 finished
// 3 finished
// 0 finished
// awaited 0
// awaited 1
// awaited 2
// awaited 3
// awaited 4
// 645ms elapsed
async function foo() {
const t0 = Date.now();
const p0 = randomDelay(0);
const p1 = randomDelay(1);
const p2 = randomDelay(2);
const p3 = randomDelay(3);
const p4 = randomDelay(4);
await p0;
await p1;
await p2;
await p3;
await p4;
setTimeout(console.log, 0, `${Date.now() - t0}ms elapsed`);
}
foo();
// 1 finished
// 4 finished
// 3 finished
// 0 finished
// 2 finished
// 877ms elapsed
=================
性能
期约与异步函数的功能有相当程度的重叠,但它们在内存中的表示则差别很大。
期约Promise:
JavaScript 引擎会在创建期约时尽可能保留完整的调用栈。
在抛出错误时,调用栈可以由运行时的错误处理逻辑获取,因而就会出现在栈追踪信息中。
当然,这意味着栈追踪信息会占用内存,从而带来一些计算和存储成本。
异步函数async/await:
JavaScript 运行时可以简单地在嵌套函数中存储指向包含函数的指针,就跟对待同步函数调用栈一样。
这个指针实际上存储在内存中,可用于在出错时生成栈追踪信息。
这样就不会像之前的例子那样带来额外的消耗,因此在重视性能的应用中是可以优先考虑的。
=================
实例1:读取文件
实例2:Ajax请求
promise then 方法
async await 方法
实例3:实现sleep()
async function sleep(delay) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
async function foo() {
const t0 = Date.now();
await sleep(1500); // 暂停约 1500 毫秒
console.log(Date.now() - t0);
}
foo();
// 1502
案例4:连锁传递值
async function addTwo(x) {return x + 2;}
async function addThree(x) {return x + 3;}
async function addFive(x) {return x + 5;}
async function addTen(x) {
for (const fn of [addTwo, addThree, addFive]) {
x = await fn(x);
}
return x;
}
addTen(9).then(console.log); // 19