系列文章目录
你能实现一个符合 Promises/A+ 规范的Promise吗?Yes, I promise…
文章目录
一、ES7中为什么要提出async/await标准
Promise
虽然跳出了异步嵌套的怪圈,用链式表达更加清晰,但是我们也发现如果有大量的异步请求的时候,流程复杂的情况下,会发现充满了屏幕的.then
,看起来非常吃力,而ES7
的Async/Await
的出现就是为了解决这种复杂的情况。
二、promise中链式写法需要注意的问题
首先需要明确,then链式写法的本质是一直往下传递一个新的promise
对象,也就是说,then
在下一步中接受的是上一步return
出去的promise
。
文中用到的两个例子:
const setDelay = (millisecond) => {
return new Promise((resolve, reject)=>{
if (typeof millisecond != 'number') reject(new Error('参数必须是number类型'));
setTimeout(()=> {
resolve(`我延迟了${millisecond}毫秒后输出的`)
}, millisecond)
})
}
const setDelaySecond = (seconds) => {
return new Promise((resolve, reject)=>{
if (typeof seconds != 'number' || seconds > 10) reject(new Error('参数必须是number类型,并且小于等于10'));
setTimeout(()=> {
resolve(`我延迟了${seconds}秒后输出的,是第二个函数`)
}, seconds * 1000)
})
}
1.链式写法的错误处理
then方法接收两个参数(都是回调):
- 一个是
resolve
的回调,拿到的是上一步的promise
成功的resolve
值 - 一个是
reject
的回调;那么这个错误处理和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
的捕获,后面就没错误了。
结论:
catch
写法是针对于整个链式写法的错误捕获的,而then
第二个参数是针对于上一个返回Promise
的。- 两者的优先级:就是看谁在链式写法的前面,在前面的先捕获到错误,后面就没有错误可以捕获了,链式前面的优先级大,而且两者都不是break, 可以继续执行后续操作不受影响。
- 实际写的时候,
catch
的写法会用到更多,,只需要在末尾catch
一下就可以了,因为链式写法的错误处理具有“冒泡”特性,链式中任何一个环节出问题,都会被catch
到,同时在某个环节后面的代码就不会执行了。 - 链式中的
catch
并不是终点!!catch
完如果还有then
还会继续往下走
2.Promise链式中间想返回自定义的值
用Promise
的原型方法resolve
即可:
setDelay(2).then(res => {
return Promise.resolve('自定义值');
})
.then(res => {
console.log(res);
})
3.如何跳出或停止Promise链式
不同于一般的function
的break
的方式,如果你是这样的操作:func().then().then().then().catch()
的方式,你想在第一个then
就跳出链式,后面的不想执行了,不同于一般的break;return null;return false等操作,可以说,如何停止
Promise``链,是一大难点,是整个Promise
最复杂的地方。
1.用链式的思维想,我们拒绝掉某一链,那么不就是相当于直接跳到了catch
模块吗?
setDelay(2000)
.then((result)=>{
console.log(result)
console.log('我进行到第一步的');
return setDelaySecond(1)
})
.then((result)=>{
console.log('我进行到第二步的');
console.log(result);
console.log('我主动跳出循环了');
return Promise.reject('跳出循环的信息') // 这里返回一个reject,主动跳出循环了
//当然你也可以直接抛出一个错误跳出:
//throw new Error('错误信息')
})
.then((result)=>{
console.log('我不执行');
})
.catch((mes)=>{
console.dir(mes)
console.log('我跳出了');
})
2.那有时候我们有这个需求:catch
是放在中间(不是末尾),而同时我们又不想执行catch
后面的代码,也就是链式的绝对中止,应该怎么办?
setDelay(2000)
.then((result)=>{
console.log(result)
console.log('我进行到第一步的');
return setDelaySecond(1)
})
.then((result)=>{
console.log('我进行到第二步的');
console.log(result);
console.log('我主动跳出循环了');
return Promise.reject('跳出循环的信息') // 这里直接调用Promise原型方法返回一个reject,主动跳出循环了
})
.then((result)=>{
console.log('我不执行');
})
.catch((mes)=>{
console.dir(mes)
console.log('我跳出了');
})
.then((res)=>{
console.log('我不想执行,但是却执行了'); // 问题在这,上述的终止方法治标不治本。
})
这时候最后一步then
还是执行了,整条链都其实没有本质上的跳出,那应该怎么办呢?
Promise
其实是有三种状态的:pending,resolve,rejected
,那么我们一直在讨论resolve和rejected
这2个状态,是不是忽视了pending
这个状态呢?pending
状态顾名思义就是请求中的状态,成功请求就是resolve
,失败就是reject
,其实他就是个中间过渡状态。
而我们上面讨论过了,then
的下一层级其实得到的是上一层级返回的Promise
对象,也就是说原Promise
对象与新对象状态保持一致。那么重点来了,如果你想在这一层级进行终止,是不是直接让它永远都pending
下去,那么后续的操作不就没了吗?是不是就达到这个目的了??觉得有疑问的可以参考Promise/A+
规范。
setDelay(2000)
.then((result)=>{
console.log(result)
console.log('我进行到第一步的');
return setDelaySecond(1)
})
.then((result)=>{
console.log(result);
console.log('我主动跳出循环了');
// return Promise.reject('跳出循环的信息')
// 重点在这
return new Promise(()=>{console.log('后续的不会执行')}) // 这里返回的一个新的Promise,没有resolve和reject,那么会一直处于pending状态,因为没返回啊,那么这种状态就一直保持着,中断了这个Promise
})
.then((result)=>{
console.log('我不执行');
})
.catch((mes)=>{
console.dir(mes)
console.log('我跳出了');
})
.then((res)=>{
console.log('我也不会执行')
})
这样就解决了上述,错误跳出而导致无法完全终止Promise
链的问题。
但是!随之而来也有一个问题,那就是可能会导致潜在的内存泄漏,因为我们知道这个一直处于pending
状态下的Promise
会一直处于被挂起的状态,而我们具体不知道浏览器的机制细节也不清楚,一般的网页没有关系,但大量的复杂的这种pending
状态势必会导致内存泄漏
那有没有办法即达到停止后面的链,同时又避免内存泄露呢。有的,具体可以看这里。
三、async/await
1.基于Promise的async/await
什么是async/await
呢?可以总结为一句话:async/await
是一对好基友,缺一不可,他们的出生是为Promise
服务的。
async
必须声明的是一个function
,不要去声明别的。并且async
必须紧跟着function
。
上面说到必须是个函数(function
),那么await
就必须是在这个async
声明的函数内部使用(必须是直系),否则就会报错。
2.async的本质
async
声明的函数的返回本质上是一个Promise
。
什么意思呢?就是说你只要声明了这个函数是async
,那么内部不管你怎么处理,它的返回肯定是个Promise
。
例子:
(async function () {
return '我是Promise';
//自动解析成return Promise.resolve('我是Promise');
// 等同于 return new Promise((resolve,reject)=>{ resolve('我是Promise') })
})()
// 返回是Promise
//Promise {<resolved>: "我是Promise"}
3.await的本质与例子
·await·的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖。
await
顾名思义就是等待一会,只要await
声明的Promise
异步返回,必须“等待”到有返回值的时候,代码才继续执行下去。
请记住await
是在等待一个Promise
的异步返回, 普通的异步(例如setTimeout
)是没有等待效果的。
4.async/await错误处理
因为 async
函数返回的是一个Promise
,所以我们可以在外面catch
住错误。
5. async/await的中断(终止程序)
首先我们要明确的是,Promise
本身是无法中止的,Promise
本身只是一个状态机,存储三个状态(pending,resolved,rejected
),一旦发出请求了,必须闭环,无法取消,之前处于pending
状态只是一个挂起请求的状态,并不是取消,一般不会让这种情况发生,只是用来临时中止链式的进行。
中断(终止)的本质在链式中只是挂起,并不是本质的取消Promise
请求,那样是做不到的,Promise
也没有cancel
的状态。
不同于Promise
的链式写法,写在async/await
中想要中断程序就很简单了,因为语义化非常明显,其实就和一般的function
写法一样,想要中断的时候,直接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('拒绝'))
那么就会进到错误信息里去。