Promise
对象用于表示一个异步操作的最终状态(成功/失败)及其结果值。 —— MDN
Promise
的出现使我们可以优雅地处理异步操作,脱离回调地狱
的痛苦。 有一利必有一弊
,它成为了面试必考问题之一 ,转化为另一种痛苦生活在我们身边... 当然,这是句玩笑话~ ?
上次看Promise
大概一个多月前了,花了两天的空暇时间基于Promise/A+手写了一遍,大多云里雾里。为了巩固,趁着周末又写了一遍,彻底梳理清楚✌。本文主要还是拆解实现Promise丐版
及其周边。
基本特点
新建Promise会立即执行,无法中断。
Promise有三种状态:pengding
、fulfilled
、rejected
。只有pengding -> fulfilled
和pending -> rejected
两种状态流,且状态更改后,不可再次更改。
resolve
为成功状态;reject
为失败状态。
Promise
基本使用?
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise复制代码
拆解实现Promise丐版
实现基本逻辑
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise
引入实现的丐版Promise,执行下上述 ?
//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})
执行打印出success
。
处理异步情况
//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{setTimeout(()=>{resolve('success')})})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})
执行无输出结果,打印then
函数的this.status
发现状态仍为pengding
。 这是为什么呢? 知道事件循环
的朋友们应该知道setTimeout
是异步且属于宏任务,将会在下个事件循环宏任务中执行,而p.then
为当前宏任务下的同步代码。因此,that.status
仍为pengding
,所以无输出结果。
既然这样,我们需要添加两个字段onFulfilled
和onRejected
去缓存成功和失败的回调,在resolve
或reject
时候再去执行缓存函数。
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullthis.onFulfilled=nullthis.onRejected=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value//有缓存成功回调,则执行that.onFulfilled&&that.onFulfilled(value)}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason//有缓存失败回调,则执行that.onRejected&&that.onRejected(value)}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}else{//挂载状态下缓存成功和失败回调this.onFulfilled=onFulfilledthis.onRejected=onRejected}}module.exports=Promise
改进后,执行上述异步情况代码,成功打印success
。
再在异步情况下加点料?,多次执行不同的p.then
函数。
//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{setTimeout(()=>{resolve('success')})})p.then((value)=>{console.log('第一次',value)},(reason)=>{console.log(reason)})p.then((value)=>{console.log('第二次',value)},(reason)=>{console.log(reason)})//第二次success
发现我们第一个p.then
代码没有被执行。 来分析下原因:目前我们只用一个变量去存储成功和失败的回调,当我setTimeout
执行之前,我的两个p.then
已按顺序执行完毕。那么,第二个p.then
就将覆盖第一个p.then
所赋值的回调函数,所以执行结果为第二次 success
。
改进下代码,我们用数组
的方式去存储所有的回调函数。
constPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=null//数组记录所有的成功回调this.onFulfilled=[]//数组记录所有的失败回调this.onRejected=[]functionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value//有缓存成功回调,则执行that.onFulfilled.forEach((fn)=>fn(value))}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason//有缓存失败回调,则执行that.REJECTED.forEach((fn)=>fn(reason))}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}else{//挂载状态下缓存所有的成功和失败回调this.onFulfilled.push(onFulfilled)this.onRejected.push(onRejected)}}module.exports=Promise
处理链式调用及值穿透(同步)
Promise
的重中之重就是链式调用,主要处理的就是下述三种情况。根据Promise/A+
的思想,每次执行完Promise.then
就创建新的promise
,并把上一个then
的返回值传递给下个promise
的then
方法,就可达到链式调用及值穿透的效果。resolve
和reject
同理。
//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{//目前只处理同步链式调用resolve('success')})//返回普通值p.then((value)=>{returnvalue}).then((value)=>{console.log(value)})//报错Cannotreadproperty'then'ofundefined//返回promisep.then((value)=>{returnnewPromise((resolve,reject)=>{resolve(value)})}).then((value)=>{console.log(value)})//报错Cannotreadproperty'then'ofundefined//值穿透p.then().then((value)=>{console.log(value)})//报错Cannotreadproperty'then'ofundefined
改进代码主要部分如下:
...Promise.prototype.then=function(onFulfilled,onRejected){letthat=this//值穿透问题onFulfilled=typeofonFulfilled==='function'?onFulfilled:(value)=>valueonRejected=typeofonRejected==='function'?onRejected:(reason)=>{throwreason}constp2=newPromise((resolve,reject)=>{if(that.status===FULFILLED){//调用成功回调constx=onFulfilled(that.value)resolvePromise(x,resolve,reject)}elseif(that.status===REJECTED){//调用失败回调constx=onRejected(that.reason)resolvePromise(x,resolve,reject)}else{//挂载状态下缓存所有的成功和失败回调that.onFulfilled.push(onFulfilled)that.onRejected.push(onRejected)}})returnp2}functionresolvePromise(x,resolve,reject){//如果x是promise对象执行其then函数(参数为p2的回调函数,而非返回的x的回调函数)if(xinstanceofPromise){x.then((value)=>resolve(value),(reason)=>reject(reason))}else{//如果x是普通值resolve(x)}}module.exports=Promise
继续执行上述三个例子的代码,均可得出success
的结果。
如果是普通值,直接resolve
返回值x
。
如果是Promise
,执行x.then
。注意!!!then
函数传参是p2
的resolve
和reject
,所以执行const x = onFulfilled(that.value)
时,执行的是p2
的resolve
函数,从而将值传递下去。
值穿透在没有resolve
和reject
参数的前提下,判断入参是否是函数,不是的话,赋值默认函数。
处理链式调用及值穿透(异步)
将main.js
内逻辑更改为异步的情况。
//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{setTimeout(()=>{resolve('success')})})...
来分析下原因:首先p.then
和p.then.then
都是第一轮事件循环,所以会于setTimeout
去执行。其次,异步的情况下,我们是缓存成功和回调函数,等待resolve
或reject
的时候再去执行。但我们缓存的仅仅是回调函数,并不是一个promise
对象,所以只要再包装一层即可。
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise0
处理返回自身的情况
原生promise
会将与返回与自身相等的错误情况抛出Chaining cycle detected for promise #<Promise>
。
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise1
resolvePromise
增加p2
传参,并添加判断
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise2
执行报错p1 is not defined
,查看堆栈报错由于resolvePromise(p2, x, resolve, reject)
中的p2
未初始化。
根据promise/A+
规范提示,可巧妙利用宏任务/微任务去解决这个问题,这边我选择setTimeout
宏任务的形式。
更改代码如下:
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise3
增加错误处理
主要捕获构造函数
及then函数
的错误。用try/catch
形式捕获
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise4
拿个?验证
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise5
标准版Promise
丐版Promise
已经基本满足所有情况,但是总想能有个证书,那还得符合我们Promise/A+
规范。
npm init
npm install promises-aplus-tests --save-dev
添加包的方法
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise6
package.json
的scripts
字段更改命令为test: promises-aplus-tests promise
,并执行npm run test
。
执行,不出所料,一堆报错。根据提示需按照规范需更改resolvePromise
函数
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise7
至此,标准版Promise
更改完毕。
Promise周边
Promise.resolve
Promise.resolve(value)
返回一个给定值解析后的promise
对象。
如果当前值是promise
对象,返回这个promise
。
如果是个带有then
函数的对象,采用它的最终状态。
返回当前值作为完成状态的promise
。
手写实现
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise8
Promise.reject
返回一个带有拒绝原因的Promise
对象。
基本实现
//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise9
Promise.all
Promise.all(iterable)
Promise.all
接收一个promise的iterable
类型(Array、Map、Set)的输入,并且只返回一个promise
实例。该实例的resolve
是在所有的promise
的resolve
回调结束后执行。reject
是在任一个promise
执行reject
后就会立即抛出错误。
基本使用
//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})0
手写实现
//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})1
Promise.race
返回一个promise
,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
基本使用
//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})2
手写实现
//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})3
总结
如果觉得有帮助的,毫不吝啬地点个?呗。
作者:瑾行链接:https://juejin.cn/post/6998888561663541255著作权归作者所有。