一、为什么有Async/Await?
我们都知道已经有了Promise
的解决方案了,为什么还要ES7提出新的Async/Await标准呢?
答案其实也显而易见:Promise
虽然跳出了异步嵌套的怪圈,用链式表达更加清晰,但是我们也发现如果有大量的异步请求的时候,流程复杂的情况下,会发现充满了屏幕的then
,看起来非常吃力,而ES7的Async/Await的出现就是为了解决这种复杂的情况。
首先,我们必须了解Promise
。
二、Promise简介
2.1 Promise实例
什么是Promise
,很多人应该都知道基础概念?直接看下面的代码(全文的例子都是基于setDelaySecond
和setDelay
两个函数,请务必记住):
const setDelay = (millisecond) => {
return new Promise((resolve, reject)=>{
if (typeof millisecond != 'number') reject(new Error('参数必须是number类型'));
setTimeout(()=> {
resolve(`我延迟了${
millisecond}毫秒后输出的`)
}, millisecond)
})
}
我们把一个Promise
封装在一个函数里面同时返回了一个Promise
,这样比较规范。
可以看到定义的Promise
有两个参数,resolve
和reject
。
resolve
:将异步的执行从pending(请求)
变成了resolve(成功返回)
,是个函数执行返回。reject
:顾名思义“拒绝”,就是从请求变成了"失败",是个函数可以执行返回一个结果,但我们这里推荐大家返回一个错误new Error()
。
上述例子,你可以reject('返回一个字符串')
,随便你返回,但是我们还是建议返回一个Error对象,这样更加清晰是“失败的”,这样更规范一点。
2.2 Promise的then和catch
我们通过Promise
的原型方法then
拿到我们的返回值:
setDelay(3000)
.then((result)=>{
console.log(result) // 输出“我延迟了2000毫秒后输出的”
})
输出下列的值:“我延迟了2000毫秒后输出的”。
如果出错呢?那就用catch
捕获:
setDelay('我是字符串')
.then((result)=>{
console.log(result) // 不进去了
})
.catch((err)=>{
console.log(err) // 输出错误:“参数必须是number类型”
})
是不是很简单?好,现在我增加一点难度,如果多个Promise
执行会是怎么样呢?
2.3 Promise相互依赖
我们在写一个Promise
:
const setDelaySecond = (seconds) => {
return new Promise((resolve, reject)=>{
if (typeof seconds != 'number' || seconds > 10) reject(new Error('参数必须是number类型,并且小于等于10'));
setTimeout(()=> {
console.log(`先是setDelaySeconds函数输出,延迟了${
seconds}秒,一共需要延迟${
seconds+2}秒`)
resolve(setDelay(2000)) // 这里依赖上一个Promise
}, seconds * 1000)
})
}
在下一个需要依赖的resolve
去返回另一个Promise
,会发生什么呢?我们执行一下:
setDelaySecond(3).then((result)=>{
console.log(result)
}).catch((err)=>{
console.log(err);
})
你会发现结果是先执行:“先是setDelaySeconds
输出,延迟了2秒,一共需要延迟5秒”
再执行setDelay
的resolve
:“我延迟了2000毫秒后输出的”。的确做到了依次执行的目的。
有人说,我不想耦合性这么高,想先执行setDelay
函数再执行setDelaySecond
,但不想用上面那种写法,可以吗,答案是当然可以。
2.4 Promise链式写法
先改写一下setDelaySecond
,拒绝依赖,降低耦合性
const setDelaySecond = (seconds) => {
return new Promise((resolve, reject)=>{
if (typeof seconds != 'number' || seconds > 10) reject(new Error('参数必须是number类型,并且小于等于10'));
setTimeout(()=> {
resolve(`我延迟了${
seconds}秒后输出的,是第二个函数`)
}, seconds * 1000)
})
}
先执行setDelay
在执行setDelaySecond
,只需要在第一个then
的结果中返回下一个Promise
就可以一直链式写下去了,相当于依次执行:
setDelay(2000)
.then((result)=>{
console.log(result)
console.log('我进行到第一步的');
return setDelaySecond(3)
})
.then((result)=>{
console.log('我进行到第二步的');
console.log(result);
}).catch((err)=>{
console.log(err);
})
发现确实达到了可喜的链式(终于脱离异步嵌套苦海,哭),可以看到then
的链式写法非常优美。
2.5 链式写法需要注意的地方
这里一定要提到一点:
then
式链式写法的本质其实是一直往下传递返回一个新的Promise
,也就是说then
在下一步接收的是上一步返回的Promise
,理解这个对于后面的细节非常重要!!
那么并不是这么简单,then
的返回我们可以看出有2个参数(都是回调):
第一个回调是resolve
的回调,也就是第一个参数用得最多,拿到的是上一步的Promise
成功resolve
的值。
第二个回调是rejec
t的回调,用的不多,但是求求大家不要写错了,通常是拿到上一个的错误,那么这个错误处理和catch有什么区别和需要注意的地方呢?
我们修改上面的代码:
setDelay(2000)
.then((result)=>{
console.log(result)
console.log('我进行到第一步的');
return setDelaySecond(20)
})
.then((result)=>{
console.log('我进行到第二步的');
console.log(result);
}, (_err)=> {
console.log('我出错啦,进到这里捕获错误,但是不经过catch了');
})
.then((result)=>{
console.log('我还是继续执行的!!!!')
})
.catch((err)=>{
console.log(err);
})
可以看到输出结果是:进到了then
的第二个参数(reject
)中去了,而且最重要的是!不再经过catch
了。
那么我们把catch挪上去,写到then错误处理前:
setDelay(2000)
.then((result)=>{
console.log(result)
console.log('我进行到第一步的');
return setDelaySecond(20)
})
.catch((err)=>{
// 挪上去了
console.log(err); // 这里catch到上一个返回Promise的错误
})
.then((result)=>{
console.log('我进行到第二步的');
console.log(result);
}, (_err)=> {
console.log('我出错啦,但是由于catch在我前面,所以错误早就被捕获了,我这没有错误了');
})
.then((result)=>{
console.log('我还是继续执行的!!!!')
})
可以看到先经过catch
的捕获,后面就没错误了。