阅读 684

【循序渐进】悟透Promises/A+规范

Promise作为现代化JavaScript开发的一把利器,已经渗透到我们日常开发工作的很多角落。

在日常的开发工作中,我们可能只关心Promise要怎样写或者用Promise的哪个方法可以解决我们的功能需求,很少回去思考Promise是如何实现的?为什么这样用就一定能达到我们想要的效果?还有没有更好的实践方式呢?

如果你也有上面的疑问,想要搞清楚这些问题,看这篇文字就对了,本篇将会带你一起探索Promise的【规格说明书】——Promises/A+,就像很多电子产品的规格说明书一样,它会告诉你它有什么,但又和它们不一样,因为Promises/A+还会告诉我们怎么做就可以人人都实现一个符合标准的YourPromise!

Promises/A+,一个健全的、可互操作的JavaScript承诺的开放标准——来自于实现者,为了实现者”。

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers. from Promises/A+

一、Promises/A+规定了什么

Promises/A+文档目前还只有英文文档,有兴趣的同学可以透过传送门去查看一番,而本篇也会从本人的理解视角对Promises/A+进行解读和理解,可能会出现一些纰漏和错误,希望大家能够批评指正。

接下来,我们将从Promises/A+中规定了什么开始入手,一起揭开Promise内部实现的神秘面纱。

1.1 专业术语

要想能够真正理解“规格说明书”想表达的真实意图,先能够正确理解其中的专业术语是非常有必要的:

  1. promise是一个具有then属性的对象或函数,并且它的行为符合本规范;
    • 这里的promise指的是new Promise()生成的那个实例对象;
    • "本规范"泛指接下来要讲到的所有约束和实现约定。
  2. thenable是一个定义了then方法的对象或函数;
  3. value指的是任意的JavaScript值(包括undefined、thenablepromise);
  4. exception指的是使用throw表达式跑出来的值;
  5. reason指的是一个可以说明为什么这个承诺(promise)没有履行的原因值。

这些专业术语都是接下来的内容中会频繁出现的关键词,所以要保证能够正确理解这些术语的含义。

1.2 硬性要求

“硬性要求”是个人的意译,原文为"Requirements",我的理解就是一个标准的Promise构造函数创建的实例对象必须要提供以下内容,才能成为一个合格的“承诺”。

1.2.1 【Promise States】

即Promise的内部状态。众所周知,Promise内部只有三个状态:pendingfulfilledrejected

  • pending状态,意为“正在打开”,可能会变成fulfilledrejected;
  • fulfilled状态,意为已履约(即操作正常结束),一定不能再转换为别的状态;此时一定有一个value,并且这个value不会再被改变;
  • rejected状态爱,意为已驳回(即操作发生了异常),同样的一定不能再转换为别的状态;此时一定有一个reason,并且这个reason不会再改变。

这里的"不会再改变",指的是【不变的定义】,即浅比较(===),并不是深层不变。

浅比较,即基本类型的数据比较值是否全等,而复杂类型的数据只对比其对应的堆地址是否变化,并不会比较堆里面的内容是否发生变化。

1.2.2【"then"方法】

一个promise必须以供一个then方法以访问其内部的valuereason

then方法接收两个参数:

promise.then(onFulfilled, onRejected)
复制代码
1.2.2.1 onFulfilledonRejected都是可选参数:
  • 如果onFulfilled不是函数,则必须被忽略;
  • 如果onRejected不是函数,则必须被忽略。
1.2.2.2 如果onFulfilled是一个函数:
  • 它必须在promise被履约后以promisevalue作为第一个参数被调用;
  • 它在promise被履约前一定不能执行;
  • 它最多只能执行一次。
1.2.2.3 如果onRejected是一个函数:
  • 它必须在promise被驳回后以promisereason作为第一个参数被调用;
  • 它在promise被驳回前一定不能执行;
  • 它最多只能执行一次。
1.2.2.4 onFulfilledonRejected只有在执行上下文中只包含【平台代码】时才会执行

这里的【平台代码】指的是引擎、环境和Promise的实现代码。事实上,这个要求是为了确保onFulfilledonRejected可以异步执行,在事件循环完成之后,在一个新的堆栈中执行then。这种行为既可以使用setTimeoutsetImmediate这样的宏任务机制实现,也可以通过MutationObserverprocess.nextTick这样的微任务机制来实现。既然Promise的实现被认为是平台代码,那么它自身一定会包含自己的任务调度队列以处理那些被调用的处理器(handler)。

1.2.2.5 onFulfilledonRejected一定要以函数的方式被调用(同时不能包含this值)

之所以不能包含this,是因为在严格模式中,this将变为undefined,而在宽松模式下则会变为全局对象。

1.2.2.6 then可能在相同的promise上被调用多次
  • 如果(当)promise是【已履约】状态时,那么它所有的onFulfilled回调函数会以它们调用在then方法上的顺序按序执行;
  • 如果(当)promise是【已驳回】状态时,那么它所有的onRejected回调函数会以它们调用在then方法上的顺序按序执行。
1.2.2.7 then方法必须返回一个promise :
promise2 = promise1.then(onFulfilled, onRejected);
复制代码
  • 如果onFulfilledonRejected返回了一个值x,那么需要执行【Promise解决过程】[[Resolve]](promise2, x)
  • 如果onFulfilledonRejected抛出了一个错误epromise2必须以e作为reason驳回;
  • 如果onFulfilled不是函数,并且promise1是已履约的状态,那么promise2必须使用和promise1同样的value变为已履约状态;
  • 如果onRejected不是函数,并且promise1是已驳回的状态,那么promise2必须使用和promise1同样的reason变为已驳回状态。
1.2.3 Promise解决过程

Promise解决过程是一个抽象的操作,它接收一个promise和一个value作为输入参数,我们使用[[Resolve]](promise, x)来定题它。如果x是一个thenable,假如x表现至少有某些行为类似一个Promise,promise将试图使采用x的状态,。否则,它用x值来变更为已履约状态。

只要thenable公开了一个兼容Promises/A+then方法,就可以使得Promise实现互操作。它还允许Promises/A+实现用合理的then方法“同化”不一致的实现。

标准实现[[Resolve]](promise, x),需要遵循以下步骤:

1.2.3.1 如果promisex指向了同一个对象,则需要将promiseTypeError作为reason并变更状态为已驳回;
1.2.3.2 如果x是一个Promise实例对象,需要适配它的状态:

通常,只有当x来自当前实现(即自己实现的Promise构造函数)时,才会知道它是一个真正的Promise实例对象。此条款允许使用特定于实现的方法来采用已知符合状态的Promise实例对象。

  • 如果xpending状态,promise必须也保持pending状态,直到x的状态变更为已履约或已驳回。
  • 如果(当)x是已履约状态,promise将以同样的value变更为已履约状态;
  • 如果(当)x是已驳回状态,promise将以同样的reason变更为已驳回状态。
1.2.3.3 如果x是一个对象或函数:
  • then取值为x.then

首先将引用存储到x,然后再测试、调用该引用,这样可以避免对x.then属性的多次访问。这些预防措施对于确保访问器属性的一致性非常重要,因为访问器属性的值在两次检索之间可能会改变。

  • 在对x.then进行取值时导致了抛出了错误e,那么promise将以e作为reason变为已驳回状态;
  • 如果then是一个函数,用x作为this调用它,其中第一个参数是resolvePromise,第二个参数是rejectPromise,即then.call(x, resolvePromise, rejectPromise):
    • 如果(当)resolvePromise被此时的value->y调用时,执行[[Resolve]](promise, y)
    • 如果(当)rejectPromise被此时的reason->r调用时,promise将使用r变更为以驳回状态
    • 如果resolvePromiseresolveReject都被调用过了,或者使用相同的参数多次调用,那么只有第一次调用才生效,将来的调用将会被忽略。
    • 如果在调用then时抛出了错误e:
      • 如果resolvePromiserejectPromise已经被调用过了,那么需要忽略它
      • 否则,promise将以e作为reason转换为已驳回状态。
  • 如果then不是一个函数,promisex作为value转为已履约状态
1.2.3.4 如果x不是一个对象或函数,promisex作为value转为已履约状态

如果一个Promise实例对象被thenable解析,这个thenable参与了一个循环的thenable链,这样[[Resolve]](promise, thenable)的递归性质最终导致[[Resolve]](promise, thenable)再次被调用,遵循上述算法将导致无限递归。我们鼓励实现(但不是必需的)检测这种递归并以信息性类型错误作为原因拒绝Promise实例对象。

实现不应该对链的深度设置任意的限制,并且假设超过这个任意的限制,递归将是无限的。只有真正的循环才会导致TypeError;如果遇到无限个独特的thenable链,则永远递归是正确的行为。

至此,Promises/A+规范就到此为止了,我们可以发现Promises/A+规范中只规定了一个Promise实例对象应该有的内部状态和一些状态描述,而原型方法中只对then方法进行了实现描述,并没有介绍诸如catchfinally等原型方法,更没有涉及Promise.resolvePromise.rejectPromise.all等静态方法。

1.3 ES6中的Promise

Promises/A+规范并不等同于ES6中的Promise。在Promises/A+规范中,并没有规定如何创建Promise实例对象的方法,而在ES6中使用new Promise只是对规范的一种实现方式,在兼容Promises/A+规范的基础上,做了自己的扩充。

因此,本篇的最终目的也是在充分了解Promises/A+规范的基础上,手写出一套和规范兼容的MyPromise,同时也需要进行一些有必要的扩充,向ES6中Promise的风格去实现,从而可以对Promise特性和不同API的使用场景有更深刻的理解和自我总结。

二、实现Promises/A+规范

在开始之前,有必要澄清一点,对于一个有丰富的Promise编程经验的开发者来说,实现Promises/A+规范是一个有益的锻炼和较高的提升,但是如果你是一个初学者,想通过实现Promises/A+去入门Promise,或者认为在实现了Promises/A+规范后会对Promise的理解能够得到质的提升,那么你可能会败兴而归。

从前文的Promises/A+的规格说明书通篇看下来,你会发现它内容精简,实现难度低。甚至具体的if elseif else的处理细节都给你描述的一清二白,缺乏相应的设计目的和思考内容,更没有介绍典型的应用场景。

因此,Promises/A+并不是一个很好的入门Promise的好材料,甚至会让你钻进牛角尖。

2.1 定义Promise状态

Promise有三个状态:pendingfulfilledrejected。且状态之间的转换有一定的规则约束。具体参见Promise State

代码实现上大概是:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function MyPromise () {
    this.state = PENDING
    this.value = undefined
    this.reason = undefined

     // 规则2.1.1 & 2.1.2
    const resolve = (value) => {
        if (this.state === PENDING) {
            this.state = FULFILLED
            this.value = value
        }
    }
    // 规则2.1.1 & 2.1.3
    const reject = (reason) => {
        if (this.state === PENDING) {
            this.state = REJECTED
            this.reason = reason
        }
    }
}
复制代码

有3个常量:pendingfulfilledrejected

一个MyPromise构造函数,内部管理着statevaluereason3个实例属性;

state只能从pending转换为另外两个状态中的一个,并在对应的状态转换过程中,变更valuereason

以上,便是一个自定义Promise的状态管理实现,当然你也可以有自己的实现方式。

MyPromise中的resolvereject使用箭头函数的格式创建,是为了绑定当前作用域下的this,你可以有自己的实现方式。

2.2 then方法

2.2.1 一个promise必须以供一个then方法以访问其内部的valuereason

那我们就要给MyPromise增加一个原型方法then:

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    
}
复制代码
2.2.2 onFulfilledonRejected都是可选参数,如果没有则必须被忽略;onFulfilledonRejected都是函数,最多只能执行一次:
  • onFulfilled的第一个参数是valueonRejected的第一个参数是reason
  • onFulfilledpromise的状态变为fulfilled后被调用;
  • onRejectedpromise的状态变为fulfilled后被调用。

落实到代码上可以是:

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    if (typeof onFulfilled === 'function' && this.state === FULFILLED) {
        onFulfilled(this.value)
    }
    if (typeof onRejected === 'function' && this.state === REJECTED) {
        onRejected(this.reason)
    }
}

复制代码
2.2.3 onFulfilledonRejected只有在执行上下文中只包含【平台代码】时才会执行必须

因为我们不是在JS引擎里去实现一个Promise,而是使用JavaScript去实现,因此,如果我们要向提供一个【平台代码】的话,在JavaScript中我们可以通过setTimeoutnextTick等去间接实现,我们选用setTimeout:

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    if (typeof onFulfilled === 'function' && this.state === FULFILLED) {
        setTimeout(() => {
            onFulfilled(this.value)
        })
    }
    if (typeof onRejected === 'function' && this.state === REJECTED) {
        setTimeout(() => {
            onRejected(this.reason)
        })
    }
}
复制代码
2.2.4 onFulfilledonReject必须以函数的方式被调用,并且没有this值:

根据规范的提示,在严格模式下this会等于undefined,而在宽松模式下则会等于全局变量,因此我们需要给onFulfilledonRejected绑定this值为空,即:

...
onFulfilled.call(null, this.value)
...
复制代码
2.2.5 then可能在相同的promise上被调用多次:

即一个promise实例对象可能会绑定多次then方法调用时传入的onFulfilledonReject方法,而且在变更状态时,它们要以绑定时的顺序按序执行。

因此,我们需要在实例中维护对应的绑定队列,在then方法中将它们推入相应的队列,同时也要保证在状态变更时按序执行:

function MyPromise (executor) {
    this.state = PENDING
    this.value = undefined
    this.reason = undefined
    this.fulfilledQueue = []
    this.rejectedQueue = []
    
    const resolve = (value) => {
        if (this.state === PENDING) {
            setTimeout(() => {
                this.state = FULFILLED
                this.value = value
                this.fulfilledQueue.forEach(callback => callback())
            })
        }
    }
    const reject = (reason) => {
        if (this.state === PENDING) {
            setTimeout(() => {
                this.state = REJECTED
                this.reason = reason
                this.rejectedQueue.forEach(callback => callback())
            })
        }
    }

    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    if (typeof onFulfilled === 'function') {
        if (this.state === FULFILLED) {
            setTimeout(() => {
                onFulfilled.call(null, this.value)
            })
        } else if (this.state === PENDING) {
            this.fulfilledQueue.push(setTimeout(() => {
                onFulfilled.call(null, this.value)
            }))
        }
        
    }
    if (typeof onRejected === 'function') {
        if (this.state === REJECTED) {
            setTimeout(() => {
                onRejected.call(null, this.reason)
            })
        } else if () {
            this.rejectedQueue.push(setTimeout(() => {
                onRejected.call(null, this.reason)
            }))
        }
    }
}
复制代码
2.2.6 then方法必须返回一个promise :

在此基础上,我们需要对then方法需要进一步处理和完善,代码如下:

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    // 规范 2.2.7.3
    onFulfilled = typeof onFulfilled === 'function' onFulfilled : x => x
    // 规范 2.2.7.4
    onRejected = typeof onRejected === 'function' onRejected : r => {throw r}
    
    const promise = new Promise((resolve, reject) => {
        if (this.state === FULFILLED) {
            setTimeout(() => {
                try {
                    const x = onFulfilled.call(null, this.value)
                    // 规则 2.2.7.1
                    [Promise Resolution Procedure](promise, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
        } else if (this.state === REJECTED) {
            setTimeout(() => {
                try {
                    const x = onRejected.call(null, this.reason)
                    // 规则 2.2.7.1
                    [Promise Resolution Procedure](promise, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
        } else {
            this.fulfilledQueue.push(setTimeout(() => {
                try {
                    const x = onFulfilled.call(null, this.value)
                    [Promise Resolution Procedure](promise, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            }))
            this.rejectedQueue.push(setTimeout(() => {
                try {
                    const x = onRejected.call(null, this.reason)
                    [Promise Resolution Procedure](promise, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            }))
        }
    })
    return promise
}
复制代码

其中,方法开头的三目运算处理兼顾了规范2.2.1和2.2.7.3和2.2.7.4的写法;try...catch是为了满足规则2.2.7.2。

then方法的实现细节基本上已经完成了,但是在最后的代码中出现了[Promise Resolution Procedure],其实它只是一个处理方法的代称,该方法用来处理onFulfilledonRejected的返回值,以保证可以兼容thenable这种数据,同时也对其他的不同场景进行了针对性的处理方式。

下面,就让我们来一睹[Promise Resolution Procedure]的风采。

2.3 Promise Resolution Procedure

Promise Resolution Procedure的具体解释可以看上文的原文翻译。

[Promise Resolution Procedure]的定义形如[[Resolve]](promise, x),即该方法接收两个参数:promisex,而具体细则中又会改变promise的状态,因此我们需要在此基础上扩展promiseresolvereject方法以改变其内部状态:

function resolvePromise (promise, x, resolve, reject) {

}

复制代码

接下来,我们重点关注下它的一些实现细节:

2.3.1 如果promisex指向同一个对象

此时需要创建一个TypeError的实例对象,并将其作为promisereason,同时变更promise为已驳回状态:

if (promise === x) {
    reject(new TypeError('Channing Cycle Error'))
    return
}
复制代码
2.3.2 如果x是一个MyPromise的实例对象

x是不是MyPromise的实例对象,可以使用instanceOf操作符来判断,同时也意味着x的操作是异步的,因此我们需要 落实到代码上,可以这样处理:

if (x instanceOf MyPromise) {
    // 对x状态的追踪,只需要调用其then方法即可
    // 其中,对于x resolve出来的结果需要继续调用resolvePromise进一步处理
    x.then(y => resolvePromise(x, y, resolve, reject), err => reject(err))
}
复制代码
2.3.3 如果x是一个对象或函数

x是一个对象或函数的情况下,再处理x.then是一个函数时,是为了能够兼容thenable类型的对象,保证一定的可扩展性。 而对于x.then不是一个函数时只需要把x作为一个普通的对象处理即可:

if (x && (typeof x === 'object' || typeof x === 'function')) {
    let used
    try {
        let then = x.then
        if (typeof then === 'function') {
            then.call(x, y => {
                if (used) return
                used = true
                resolvePromise(promise, y, resolve, reject)
            }, r => {
                if (used) return
                used = true
                reject(r)
            })
        } else {
            resolve(x)
        }
    } catch (e) {
        if (used) return
        used = true
        reject(e)
    }
}

复制代码

其中used变量的使用时为了满足规则2.3.3.3.3和2.3.3.3.4.1,即避免重复调用resolvereject

2.3.4 如果x不是一个对象或函数

这条最简单,直接resolve(x)即可。

所以,最终版的[Promise Resolution Procedure]的实现代码大概是下面这个样子的:

function resolvePromise (promise, x, resolve, reject) {
    if (promise === x) {
        reject(new TypeError('Channing Cycle Error'))
        return
    }
    if (x instanceOf MyPromise) {
        x.then(y => resolvePromise(x, y, resolve, reject), r => reject(r))
        return
    }
    if (x && (typeof x === 'object' || typeof x === 'function')) {
        let used
        try {
            const then = x.then
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (used) return
                    used = true
                    resolvePromise(promise, y, resolve, reject)
                }, r => {
                    if (used) return
                    used = true
                    reject(r)
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (used) return
            used = true
            reject(e)
        }
    } else {
        resolve(x)
    }
}
复制代码

以上便是Promises/A+规格说明书的全部内容。

三、扩展MyPromise

Promises/A+规范中只描述了then方法相关内容,而我们在日常工作过程中使用的Promise则是ES6+在兼容规范的基础上进而的扩展和自主实现。

本着要彻底搞懂Promise的初心,下面我们要在以上掌握的理论基础上自己去实现ES6+中定义的Promise所具备的一些常用方法,如:Promise.prototype.catchPromise.prototype.finallyPromise.resolvePromise.rejectPromise.allPromise.race

在扩展这些方法时,为了尽量和ES6+保证一致性,我们将以MDN中对这些方法的描述为目标,保证一定的兼容性。

3.1 静态方法resolve

Promise.resolve(value)方法返回一个以给定值解析后的Promise对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是thenable(即带有"then" 方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。

以上是MDN对Promise.resolve(value)方法的描述,下面我们试着来实现它:

MyPromise.resolve = function (value) {
    // 目标值是一个promise,直接返回
    if (value instanceOf MyPromise) {
        return value
    }
    return new Promise((resolve, reject) => {
        if (value && (typeof value === 'object' || typeof value === 'function')) {
            // 目标值是thenable,则“跟随”它的状态
            if (typeof value.then === 'function') {
                setTimeout(() => {
                    value.then(resolve, reject)
                })
            } else {
                resolve(value)
            }
        } else {
            resolve(value)
        }
    })
}
复制代码

优先实现Promise.resolve是有目的的,因为在Promise.prototype.finally的实现中会复用到它的内部处理。

3.2 静态方法reject

静态函数Promise.reject(reason)返回一个被拒绝的Promise对象。

如果你真正理解了Promise对内部状态的管理,那么答案呼之欲出:

MyPromise.reject = function (reason) {
    return new Promise((resolve, reject) => {
        reject(reason)
    })
}
复制代码

惊喜&意外!

3.3 静态方法all

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。

根据以上“需求文档”,我们可以实现一下代码:

MyPromise.all = function (iterable) {
    return new Promise((resolve, reject) => {
        const result = []
        if (iterable.length === 0) {
            resolve(result)
        }
        let index = 0
        for (for promise of iterable) {
            // 在这里需要先使用MyPromise.resolve去处理下promise,因为promise不一定是一个MyPromise实例对象,需要统一处理方法
            MyPromise.resolve(promise).then(value => {
                index++
                result.push(value)
                if (index === iterable.length) {1
                    resolve(resolve(result)) 
                }
            }, reject)
        }
    })
}

复制代码

3.4 静态方法race

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

race中,有一点需要尤其注意:如果传的迭代是空的,则返回的 promise 将永远等待。

另外,如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

MyPromise.race = function (iterable) {
    return new Promise((resolve, reject) => {
        const result = []
        // 参数是空值,则将永远等待
        if (iterable.length === 0) {
            return
        }
        let index = 0
        for (for promise of iterable) {
            // 这路也需要先使用MyPromise.resolve()来处理下promise
            return MyPromise.resolve(promise).then(resolve, reject)
        }
    })
}

复制代码

3.5 原型方法catch

MDN这样描述catchcatch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。

出门遇到惊喜,MDN已经告诉了我们实现catch的答案:

MyPromise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected)
}
复制代码

catch的实质就是使用thenpromise实例对象内注册一个rejected的回调函数,当promise的状态变为rejected时便会调用catch()指定的onRejected方法。

3.6 原型方法finally

如果你想在 promise 执行完毕后无论其结果怎样都做一些处理或清理时,finally() 方法可能是有用的。

finally() 虽然与 .then(onFinally, onFinally) 类似,它们不同的是:

  • 调用内联函数时,不需要多次声明该函数或为该函数创建一个变量保存它;
  • 由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况;
  • Promise.resolve(2).then(() => {}, () => {})resolved的结果为undefined)不同,Promise.resolve(2).finally(() => {}) resolved的结果为 2;
  • 同样,Promise.reject(3).then(() => {}, () => {}) (resolved 的结果为undefined), Promise.reject(3).finally(() => {}) rejected 的结果为 3

因此,我们可以知道:finally()的回调函数不接收入参;finally()执行结束需要返回它上一个promise的结果(valuereason)。


MyPromise.prototype.finally = function (finalCallback) {
    return this.then(value => {
        return Promise.resolve(finalCallback()).then(() => value)
    }, reason => {
        return Promise.resolve(finalCallback()).then(() => reason)
    })
}

复制代码

至此,我们已经基本上实现了ES6+中Promise的大部分原型方法和静态方法。

在实现这些方法的过程中,我们会发现,只要Promise的原型方法,就一定会借用then方法去绑定onFulfilledonRejected方法到promise实例上,以响应不同的状态转换;而静态方法则一定会返回一个新的Promise实例对象,在Promise的回调函数中自定义管理这个实例对象的状态转换。

所以,Promise的核心主要体现在两方面:管理promise实例对象的状态和绑定回调函数以响应promise实例对象的状态变化。

四、总结

对于手写Promises/A+的感触还是挺多的,刚开始作为一个前端小白在刷掘金的过程中偶然发现有很多标题为xxx行实现Promise的文章,点进去之后一次次的惊叹于作者的学识渊博,对他们羡慕不已。

在此之后,励志自己也要能够手写实现一个Promise,因此就跟它刚上了。 连续半个月的时间里,每天都会抽出一些时间来学习Promise,甚至仿写他们的代码,搞到一半的时候就会想他们什么会想到这样去写就能实现这个功能?为什么是这样的?为什么是那样的?直到一天,我发现有Promises/A+这个东西的存在,于是便豁然开朗,又花了几天的时间终于理清楚了Promise的真实面目。

那段时间,真的是晚上睡觉做梦都在写Promise。

整个搞下来最大的感悟就是,想要学习一个东西一定要搞清楚它的真实面目和适用场景,不要一味的人云亦云,跟风随大流;只有搞清楚了整件事情的真面目,最后再循序渐进的攻克它,你会有一个很大提高。

加油吧打工人!

文章分类
前端
文章标签