这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战
四、async/await 进阶
4.1 async/await 优势实战
4.1.1 抛弃冗余的 then 链式代码
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
用 Promise 方式来实现这三个步骤的处理:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
});
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
如果用 async/await 来实现:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
}
doIt();
4.1.2 更加优雅的中间值
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
});
}
doIt();
用 async/await 来写:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
}
doIt();
// step1 with 300
// step2 with 300
// step3 with 300
// result is 500
4.1.3 更加易于调试
因为没有代码块,所以不能在一个返回的箭头函数中设置断点。如果你在一个 .then
代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的 .then
代码块,因为调试器只能跟踪同步代码的每一步。
现在,如果使用 async/await
,你就不必再使用箭头函数。你可以对 await 语句执行步进操作,就好像他们都是普通的同步语句一样。
4.2 async/await 错误处理
因为 async 函数返回的是一个 Promise,所以我们可以在外面 catch 住错误。
const demo = async () => {
const result = await setDelay(1000);
console.log(result);
console.log(await setDelaySecond(2));
console.log(await setDelay(1000));
console.log('完成了');
}
demo().catch(err => {
console.log(err);
})
还可以使用 try...catch
语句,这时候就不需要在外面 catch 了。
(async () => {
try {
const result = await setDelay(1000);
console.log(result);
console.log(await setDelaySecond(2));
console.log(await setDelay(1000));
console.log('完成了');
} catch (e) {
console.log(e); // 这里捕获错误
}
})()
但是 try...catch
好像只能包裹代码块,如果需要拆分开分别处理,不想因为一个的错误就整个 process 都 crash 掉了,那么难道要写一堆 try...catch
吗?
(async () => {
const result = await setDelay(1000).catch(err => {
console.log(err)
});
console.log(result);
const result1 = await setDelaySecond(12).catch(err => {
console.log(err)
})
console.log(result1);
console.log(await setDelay(1000));
console.log('完成了');
})()
输出结果:
我延迟了1000毫秒后输出的
Error: 参数必须是number类型,并且小于等于10
at Promise (test4.html:19)
at new Promise (<anonymous>)
at setDelaySecond (test4.html:18)
at test4.html:56
undefined
我延迟了1000毫秒后输出的
完成了
这样就算有错误,也不会影响后续的操作。但是 await 后面又跟着 catch,非常混乱。可以改进一下,封装一下提取错误的代码函数:
function to(promise) {
return promise.then(data => {
return [null, data];
}).catch(err => [err]);
}
返回的是一个数组,第一个是错误,第二个是异步结果,使用如下:
(async () => {
// es6的写法,返回一个数组
// 第一个是错误信息,第二个是then的异步返回数据
// 这里要注意一下重复变量声明可能导致问题
[err, result] = await to(setDelay(1000))
// 如果err存在就是有错,不想继续执行就抛出错误
if (err) throw new Error('出现错误,同时我不想执行了');
console.log(result);
[err, result1] = await to(setDelaySecond(12))
// 还想执行就不要抛出错误
if (err) console.log('出现错误,同时我想继续执行', err);
console.log(result1);
console.log(await setDelay(1000));
console.log('完成了');
})()
4.3 async/await 终止程序
Promise 本身是无法中止的,Promise 本身只是一个状态机,存储三个状态(pending,resolved,rejected),一旦发出请求了,必须闭环,无法取消,之前处于 pending 状态只是一个挂起请求的状态,并不是取消,一般不会让这种情况发生,只是用来临时中止链式的进行。
async/await 想要中断的时候,直接 return 一个值就行,null,空,false都是可以的。
let count = 6;
const demo = async () => {
const result = await setDelay(1000);
console.log(result);
const result1 = await setDelaySecond(count);
console.log(result1);
if (count > 5) {
return '我退出了,下面的不进行了';
// return;
// return false; // 这些写法都可以
// return null;
}
console.log(await setDelay(1000));
console.log('完成了');
};
demo().then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
实质就是直接 return 返回了一个 Promise,相当于 return Promise.resolve('我退出了下面不进行了')
,当然你也可以返回一个“拒绝”:return Promise.reject(new Error('拒绝'))
那么就会进到错误信息里去。