改造上节案例
-
通过 Generator 函数 和 yield 让异步代码看起来像同步代码一样执行;但是这样里面存在的一个问题就是生成器函数直接执行,需要手动处理;为了解决深层回调的问题借助了 co 库去执行生成器函数,从而解决了回调地狱的问题;
// name.txt 的内容 age.txt // age.txt 的内容 10 const fs = require('fs').promises; // 代码编写更像是同步的,更友好的编码方式 (执行还是异步的) function* read() { try { let name = yield fs.readFile('name.txt', 'utf8'); let age = yield fs.readFile(name, 'utf8'); return age; } catch (error) { console.log(error); } } // 手写 co,异步迭代:co 有现成的库,koa、express 的作者 tj const co = it => { // 需要返回一个 new Promise() 实例 return new Promise((resolve, reject) => { // 异步迭代的核心是 => 回调函数 function next(data) { let { value, done } = it.next(data); if (!done) { // 默认成功后会调用 next 方法,将结果传递到 next 函数中 // 如果 yield 返回的不是一个 Promise 对象时,我们对 value 使用了 Promise.resolve() 进行了包装,这样就可以处理返回一个普通值时没有 then 方法的问题 Promise.resolve(value).then(next, reject); } else { resolve(value); } } next(); }); } // 测试:co 会将生成器的结果转成 promise co(read()) .then(data => console.log(data)) .catch(err => console.log(err))
-
其实最希望的是能像执行普通函数一样直接调用 getValue 就能执行并得到结果;async/await 的出现就是为了抹平在调用时所做的额外步骤;
// name.txt 的内容 age.txt // age.txt 的内容 10 const fs = require('fs').promises; async function getValue() { try { let name = await fs.readFile('name.txt', 'utf8'); let age = await fs.readFile(name, 'utf8'); return age; } catch (error) { console.log(error); } } getValue(); // 控制台打印的值是:10
async、await 用法
定义一个异步函数时需要使用 async 和 function 关键字一起来完成,类似生成器函数中的 yield 来暂停异步任务,在 async 函数中使用 await 关键去等待异步任务返回的结果;
async 函数其本质是 Promise + Generator 函数组成的语法糖,它为了减少了 Promise 的链式调用,解放了 Generator 函数的单步执行;
async
-
async 函数返回的是一个 promise 对象,return 的值就是 promise 实例的值,最主要的作用是配合 await 使用的,一旦在函数中使用 await,那么当前的函数必须用 async 修饰;
-
示例代码:
JavaScriptJavaScriptasync function fn() { return 10; } console.log(fn()); // Promise { 10 }
async function fn() { // 因为加入了 setTimeout 异步操作,直接向下执行,没有 return,fn 的值为 undefined setTimeout(_ => { return 10; }, 1000); } console.log(fn()); // Promise { undefined }
await
-
await 只能处理 promise 为 fulfilled (成功) 的时候,await 是异步编程,当代码执行到此行,构建一个异步的微任务,等待 promise 返回结果,否则 await 下面的代码都不能执行 (await 下面的代码也都被列到任务队列中,相当于 promise.then);
- 若 promise 返回 rejected 失败 状态,则 await 不会接收其返回结果,await 下面的代码也不会再继续执行;
- 若 promise 返回 fulfilled 成功 状态,则继续执行下面的代码;
-
示例代码:
JavaScriptJavaScriptJavaScriptasync function fn() { console.log(1); let AA = await Promise.resolve(100); console.log(AA); } fn(); console.log(2); // 1 2 100
let p1 = Promise.resolve(100); let p2 = new Promise(resolve => { setTimeout(_ => { resolve(200); }, 1000); }); async function fn() { console.log(1); // await 以下的所有代码都不执行,放入事件队列微任务中,1s 后返回输出 200 let result = await p2; console.log(result); // await 以下的所有代码都不执行,放入事件队列微任务中,p1 执行完后返回输出 100 let AA = await p1; console.log(AA); } fn(); console.log(2); // 1 2 200 100
async function fn() { let reason = await Promise.reject(3); console.log(reason); } fn(); // 报错:Uncaught (in promise) 3
错误捕获
-
使用 try…catch 捕获错误,多个任务时无法细粒度的定位出问题 (try…catch 只能捕获同步的错误);
const task = function (num) { return new Promise((resolve, reject) => { setTimeout(() => { if (num === 300) { reject('throw error') } else { resolve('hello'); } }, 1000) }) } // 如果这样写,判断不出来哪一个 task 报错了,不能进行细粒度的定位 async function foo() { try { let res1 = await task(100); let res2 = await task(200); let res3 = await task(300); } catch(e) { console.log(e); } } foo() // throw error
-
使用 catch (最佳实践)
const task = function (num) { return new Promise((resolve, reject) => { setTimeout(() => { if (num === 300) { reject('throw error') } else { resolve('hello'); } }, 1000) }) } async function foo() { let res1 = await task(100).catch(err => console.log('res1', err)); let res2 = await task(200).catch(err => console.log('res2', err)); let res3 = await task(300).catch(err => console.log('res3', err)); } foo() // res3 throw error
滥用 async/await
-
其实这里是一个坑,很多时候 async/await 都会被滥用导致程序卡顿,执行时间过长;
// 如果后一个任务依赖前一个任务这样写完全没问题,但是如果是三个独立的异步任务,那这样写就会导致程序执行时间加长 async function foo() { let res1 = await task(100); let res2 = await task(200); let res3 = await task(300); return { res1, res2, res3 } } foo()
-
针对没有关联的异步任务需要把它们解开;
async function foo() { let res1Promes = task(100); let res2Promes = task(200); let res3Promes = task(300); let res1 = await res1Promes; let res2 = await res2Promes; let res3 = await res3Promes; console.log({ res1, res2, res3 }) return { res1, res2, res3 } } foo(); // { res1: 'hello 100', res2: 'hello 200', res3: 'hello 300' }
面试题
第 1 题(头条、字节)
-
题目
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); // 浏览器环境:识别 await 后面跟的是 promise 的话默认就会直接调用 promise.then // 等价于 async2().then(() => console.log('async1 end')) // node 低版本环境:node 识别不出 await,则直接用 promsie 的 resolve 包裹起来,resole 里面是 promise 会调用 then,然后再调用 then(() => console.log('async1 end')),相当于调了两次 then,所以结果和浏览器的不太一致 // new Promise((resolve, reject) => resolve(async2())).then(() => console.log('async1 end')); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(function () { console.log('setTimeout'); }, 0) async1(); new Promise(function (resolve) { console.log('promise1'); resolve(); }).then(function () { console.log('promise2'); }); console.log('script end'); // 浏览器环境执行: // script start // async1 start // async2 // promise1 // script end // async1 end // promise2 // setTimeout
-
图解
第 2 题(字节)
-
题目
console.log(1); setTimeout(_ => { console.log(2); }, 1000); async function fn() { console.log(3); setTimeout(_ => { console.log(4); }, 20); return Promise.reject(); } async function run() { console.log(5); await fn(); console.log(6); } run(); // 需要执行 150MS 左右 for (let i = 0; i < 90000000; i++) { } setTimeout(_ => { console.log(7); new Promise(resolve => { console.log(8); resolve(); }).then(_ => { console.log(9); }); }, 0); console.log(10); // 1 // 5 // 3 // 10 // 4 // 7 // 8 // 9 // 2
-
图解
第 3 题
async function m1() {
return 1;
}
async function m2() {
const n = await m1();
console.log(n);
return 2;
}
async function m3() {
const n = m2(); // 没有 await,返回值是 Promise<pending>
console.log(n);
return 3;
}
m3().then((n) => {
console.log(n);
});
m3();
console.log(4);
// Promise { <pending> }
// Promise { <pending> }
// 4
// 1
// 3
// 1
第 4 题
var a;
var b = new Promise((resolve, reject) => {
console.log('promise1');
setTimeout(() => {
resolve();
}, 1000);
})
.then(() => {
console.log('promise2');
})
.then(() => {
console.log('promise3');
})
.then(() => {
console.log('promise4');
});
a = new Promise(async (resolve, reject) => {
console.log(a);
await b;
console.log(a);
console.log('after1');
await a;
resolve(true);
console.log('after2');
});
console.log('end');
// promise1
// undefined
// end
// promise2
// promise3
// promise4
// Promise { <pending> }
// after1
计算机网络🛜 常见请求方法
上一篇