Promise源码的实现(完美符合Promises/A+规范)

148 阅读5分钟

术语

  • promise:是具有符合本规范的then方法的对象或函数。
  • thenable:是定义了then方法的对象或函数。
  • value:是任何合法的JavaScript值(包括undefinedthenablepromise)。
  • exception:是使用throw语句抛出的值。
  • reason:是表示promise被拒绝的原因的值。

要求

Promise的状态

  • 一个promise必须处于以下三种状态之一:pending(等待)、fulfilled(已完成)或rejected(已拒绝)。
  • 当且仅当promise处于pending(等待)时,才可被修改。
  • 处于fulfilled状态下的promise不可转换为其他状态,且必须有一个不可更改的值(value)。
  • 处于rejected状态下的promise不可转换为其他状态,且必须有一个不可更改的原因(reason)。

then方法

promise必须提供一个then方法以访问其当前或最终的值或原因。

一个promise的then方法接受两个参数:

promise.then(onFulfilled, onRejected)
  • onFulfilled:

    • onFulfilled是可选参数。

    • 我们期望onFulfilled是一个函数,如果onFulfilled不是一个函数,它必须被忽略。

    • 它必须在promise实现后调用,并以promise的值作为其第一个参数。

    • promise实现之前不得调用它。

    • 它不能被调用多次。

onRejected:

  • onRejected是可选参数。

  • 我们期望onRejected是一个函数,如果onRejected不是一个函数,它必须被忽略。

  • 它必须在promise实现后调用,并以promise的原因作为其第一个参数。

  • promise 被拒绝之前不得调用它。

  • 它不能被调用多次。

  • onFulfilledonRejected不能在执行上下文堆栈中只包含平台代码之前调用。

  • onFulfilledonRejected必须作为函数被调用(即没有this值)。

  • then方法可以在同一个promise上多次调用。

    • 如果/当promise被实现时,所有相应的onFulfilled回调函数必须按照它们发起then调用的顺序执行。
    • 如果/当promise被拒绝时,所有相应的onRejected回调函数必须按照它们发起then调用的顺序执行。
  • then方法必须返回一个promise。

    • promise2 = promise1.then(onFulfilled, onRejected);
      
    • 如果onFulfilledonRejected返回一个值x,则运行Promise Resolution Procedure [[Resolve]](promise2, x)

    • 如果onFulfilledonRejected抛出异常e,则promise2必须以e作为原因被拒绝。

    • 如果onFulfilled不是一个函数且promise1被实现,则promise2必须以与promise1相同的值被实现。

    • 如果onRejected不是一个函数且promise1被拒绝,则promise2必须以与promise1相同的原因被拒绝。

Promise解决过程

Promise解决过程是一个抽象操作,接受一个promise和一个值作为输入,我们将其表示为[[Resolve]](promise, x)。如果x是一个thenable,它尝试使promise采用x的状态,假设x至少在某种程度上像一个promise。否则,它使用值x来实现promise

对thenable的处理允许promise实现进行互操作,只要它们暴露符合Promises/A+的then方法。它还允许Promises/A+实现通过合理的then方法来“吸收”不符合规范的实现。

运行[[Resolve]](promise, x),执行以下步骤:

  1. 如果promisex引用同一个对象,则以TypeError为原因拒绝promise
  2. 如果x是一个promise,采用其状态
    1. 如果x处于待定状态,则promise必须保持待定状态,直到x被实现或拒绝。
    2. 如果/当x被实现时,用相同的值实现promise
    3. 如果/当x被拒绝时,用相同的原因拒绝promise
  3. 否则,如果x是一个对象或函数:
    1. thenx.then
    2. 如果获取属性x .then导致抛出异常e,则以e为原因拒绝promise
    3. 如果then是一个函数,则以x作为this,第一个参数为resolvePromise,第二个参数为rejectPromise调用它。其中:
      1. 如果/当resolvePromise被调用并传入值y,运行[[Resolve]](promise, y)
      2. 如果/当rejectPromise被调用并传入原因r,以r拒绝promise
      3. 如果resolvePromiserejectPromise都被调用,或者对同一个参数进行多次调用,则第一次调用优先,任何后续调用都将被忽略。
      4. 如果调用then导致抛出异常e
        1. 如果已经调用了resolvePromiserejectPromise,则忽略它。
        2. 否则,以e为原因拒绝promise
    4. 如果then不是一个函数,则以x来实现promise
  4. 如果x不是对象或函数,则用x来实现promise

如果一个promise被解决为一个参与循环thenable链的thenable,以至于递归的[[Resolve]](promise, thenable)最终再次调用[[Resolve]](promise, thenable),按照上述算法进行将导致无限递归。实现可以选择性地检测到这种递归并以一个信息丰富的TypeError为原因拒绝promise,但不是必须的。

代码实现

class Promise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    constructor(executor) {
        this.PromiseState = Promise.PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
        const resolve = value => {
            if(this.PromiseState === Promise.PENDING){
                this.PromiseState = Promise.FULFILLED
                this.value = value
                this.onFulfilledCallbacks.forEach(callback=>callback(this.value))
            }
        }
        const reject = reason => {
            if(this.PromiseState === Promise.PENDING){
                this.PromiseState = Promise.REJECTED
                this.reason = reason
                this.onRejectedCallbacks.forEach(callback=>callback(this.reason))
            }
        }
        try{
            executor(resolve,reject)
        }catch (e) {
            reject(e)
        }
    }

    then(onFulfilled, onRejected){
        if(typeof onFulfilled !== 'function'){
            onFulfilled =  value => {
                return value
            }
        }
        if(typeof onRejected !== 'function'){
            onRejected =  reason => {
                throw reason
            }
        }
        let promise2 = new Promise((resolve,reject)=>{
            if(this.PromiseState === Promise.FULFILLED){
                setTimeout(()=>{
                    try{
                        const x = onFulfilled(this.value)
                        Promise.resolvePromise(promise2,x,resolve,reject)
                    }catch (e) {
                        reject(e)
                    }
                })
            }
            if(this.PromiseState === Promise.REJECTED){
                setTimeout(()=>{
                    try{
                        const x = onRejected(this.reason)
                        Promise.resolvePromise(promise2,x,resolve,reject)
                    }catch (e) {
                        reject(e)
                    }
                })
            }
            if(this.PromiseState === Promise.PENDING){
                this.onFulfilledCallbacks.push((value)=>{
                    setTimeout(() => {
                        try{
                            const x = onFulfilled(value)
                            Promise.resolvePromise(promise2,x,resolve,reject)
                        }catch (e) {
                            reject(e)
                        }
                    })
                })
                this.onRejectedCallbacks.push((reason)=>{
                    setTimeout(() => {
                        try{
                            const x = onRejected(reason)
                            Promise.resolvePromise(promise2,x,resolve,reject)
                        }catch (e) {
                            reject(e)
                        }

                    })
                })
            }
        })
        return promise2
    }
    catch(onRejected){
        return this.then(null,onRejected)
    }

    static resolvePromise(promise2,x,resolve,reject) {
        if(promise2 === x){
            reject(new TypeError('Chaining cycle detected for promise'))
        }

        if(x instanceof Promise){
            x.then(value => {
                Promise.resolvePromise(promise2,value,resolve,reject)
            }, reason => {
                reject(reason)
            })
        }else if(x !== null && (typeof x === 'object' || typeof x === 'function')){
            let called = false
            try{
                const then = x.then
                if(typeof then === 'function'){
                    then.call(x,value => {
                        if(called)  return
                        called = true
                        Promise.resolvePromise(promise2, value, resolve, reject)
                    }, reason => {
                        if(called)  return
                        called = true
                        reject(reason)
                    })
                }else{
                    if(called)  return
                    called = true
                    resolve(x)
                }
            }catch (e) {
                if(called)  return
                called = true
                reject(e)
            }
        }else{
            resolve(x)
        }
    }    
}

promise中的其他方法

static resolve(value){
    if(!value){
        return new Promise((resolve)=>{
            resolve()
        })
    }
    if(value instanceof Promise){
        return value
    }
    if(value && typeof value === 'object' && typeof value.then === 'function'){
        return new Promise((resolve,reject)=>{
            value.then(resolve,reject)
        })
    }
    return new Promise((resolve)=>{
        resolve(value)
    })
}

static reject(value){
    return new Promise((resolve,reject)=>{
        reject(value)
    })
}

static all(promises){
    return new Promise((resolve,reject)=>{
        try{
            promises = Array.from(promises)
        }catch (e) {
            reject(new TypeError('参数必须是一个数组'))
            return
        }
        let count = promises.length
        let results = new Array(count)
        if(count === 0){
            resolve(results)
            return
        }
        promises.forEach((promise,index)=>{
            Promise.resolve(promise).then(res=>{
                results[index] = res
                count--
                if(count === 0){
                    resolve(results)
                    return
                }
            }).catch(err=>{
                reject(err)
                return
            })
        })
    })
}

static race(promises){
    return new Promise((resolve,reject)=>{
        try{
            promises = Array.from(promises)
        }catch (e) {
            reject(new TypeError('参数必须是一个数组'))
            return
        }
        if(promises.length === 0){
            return resolve([])
        }
        promises.forEach((promise,index)=>{
            Promise.resolve(promise).then(res=>{
                resolve(res)
                return
            }).catch(err=>{
                reject(err)
                return
            })
        })
    })
}

如何判断代码是否符合promises/A+规范

在你的代码中添加(将promise替换为你自己定义的类名)

Promise.defer = Promise.deferred = function(){
    let dfd = {};
    dfd.promise = new Promise((resolve, reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}
module.exports =  Promise

在控制台运行指令npm run test

image.png