# 实现符合A+规范的Promise

# 实现简易版的Promise

我们首先来实现一个简单版的Promise,让他支持下面的简单用法




 






const p = new Promise((resolve, reject) => {
    console.log('executor')
    resolve('成功了')
})
p.then((value) => {
    console.log('状态是' + value)
}, (reason) => {
    console.log('状态是' + reason)
})

TIP

这里需要注意一些promise的一些简单特性,首先new Promise()里面的是同步代码,会立即执行

其次promise的状态一旦变为成功或者失败状态就不可以被改变了

所以上诉输出的结构是 executor 状态是成功了

接下来我们实现一款最简单的Promise




 




































const SUCCESS = 'fulfilled' // 成功的状态
const FALL = 'rejected' // 失败的状态
const PENDING = 'pending' // 等待的状态

class Promisse {
    constructor (executor) {
        this.status = PENDING
        this.value = undefined // 存放当前成功的值
        this.reason = undefined // 存放当前失败的值
        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = SUCCESS
                this.value = value
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = FALL
                this.reason = reason
            }
        }
        try { // 如果执行器的同步代码报错,进行捕获处理
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }

    then(onFullFilled, onRejected) {
        if (this.status === SUCCESS) {
            onFullFilled(this.value)
        }
        if (this.status === FALL) {
            onRejected(this.reason)
        }
    }
}

module.exports = Promisse

首先我们定义三个表示状态的常量,其次我们执行器executor对应我们new Promise(xxx)传入的xxx函数,他接收2个参数resolvereject,这2个方法的作用是缓存当前成功的状态或者失败的状态,并且修改对应的状态为成功或失败

然后我们需要注意的是executor执行的时候需要用try-catch包裹起来,因为同步代码也有可能报错

最后我们需要写一个then方法,他可以根据当前promise的状态执行对应的回调函数

# 支持异步调用

上述最简单的promise有一个小缺陷,就是不支持异步操作,如




 


















let Promise = require('./promise1')
const fs = require('fs')

const p = new Promise((resolve, reject) => {
    fs.readFile('./name.txt', 'utf8', function(err, data) {
        if (err) {
            reject(err)
        }
        resolve(data)
    })
})
p.then((value) => {
    console.log(value)
}, (reason) => {
    console.log(reason)
})
p.then((value) => {
    console.log(value)
}, (reason) => {
    console.log(reason)
})

我们知道fs.readFile是一个异步操作,也就是意味着我们同步调用then方法的时候,当前promise的状态还有可能是pending状态,接下来我们实现支持异步调用




 
















































const SUCCESS = 'fulfilled'  
const FALL = 'rejected' 
const PENDING = 'pending' 

class Promisse {
    constructor(executor) {
        this.status = PENDING
        this.value = undefined 
        this.reason = undefined 
        this.onResolveCallBacks = [] // 新
        this.onRejtectCallBacks = [] // 新
        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = SUCCESS
                this.value = value
                this.onResolveCallBacks.forEach(fn => fn())// 将缓存起来的成功的回调遍历执行
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = FALL
                this.reason = reason
                this.onRejtectCallBacks.forEach(fn => fn()) // 将缓存起来的失败的回调遍历执行
            }
        }
        try { 
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }

    then(onFullFilled, onRejected) {
        if (this.status === SUCCESS) {
            onFullFilled(this.value)
        }
        if (this.status === FALL) {
            onRejected(this.reason)
        }
        if (this.status === PENDING) { // 如果当前状态是等待状态,也就是可能是异步调用,我们将对应的回调存起来
            this.onResolveCallBacks.push(() => {
                onFullFilled(this.value)
            })
            this.onRejtectCallBacks.push(() => {
                onRejected(this.reason)
            })
        }
    }
}

module.exports = Promisse

首先我们用2个变量onResolveCallBacks onRejtectCallBacks来缓存成功和失败的回调

当我们执行then操作的时候,如果当前状态还是pending状态,那我们就需要将回调缓存起来,当异步调用了resolve或者reject的时候再将缓存遍历执行

# 实现then的链式调用

我们开发时候最常用的写法就是链式的调用promise了,如




 















let Promise = require('./promise')
const fs = require('fs')

function read(filePath){
    return new Promise((resolve, reject)=> {
        fs.readFile(filePath, 'utf8', function (err, data) {
            if (err) {
                return reject(err)
            }
            resolve(data)
        })
    })
}
read('./name.txt').then(function(data) {
    return 1000
}).then(data => {
    console.log(data)
})

但是我们promise的链式调用和Jquery的链式调用不太一样,jq是通过返回this来达到对应的效果,而我们promise的状态一旦变为成功或者失败状态就无法进行改变,所以promise链式调用的核心在于每一个then执行之后,返回一个新的promise

TIP

我们需要知道一些then的规则

如果then中返回一个不是promise的值,那么这个值会作为下一个then的参数

如果then中返回一个promise,那么这个promise成功或者失败的值会作为下一个then中的参数

我们主要修改一下then方法,让他按照对应的规则返回一个新的promise




 













































    then(onFullFilled, onRejected) {
        let newPromise
        newPromise = new Promise((resolve, reject) => {
            if (this.status === SUCCESS) {
                setTimeout(() => {
                    try {
                        let x = onFullFilled(this.value)
                        resolvePromise(newPromise, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                })
            }
            if (this.status === FALL) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(newPromise, x, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                })
            }
            if (this.status === PENDING) {
                this.onResolveCallBacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFullFilled(this.value)
                            resolvePromise(newPromise, x, resolve, reject)
                        } catch (err) {
                            reject(err)
                        }
                    })
                })
                this.onRejtectCallBacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            resolvePromise(newPromise, x, resolve, reject)
                        } catch (err) {
                            reject(err)
                        }
                    })
                })
            }
        })
        return newPromise
    }

按照Promise A+规范,我们需要写一个resolvePromise方法来处理promise的返回值




 




















function resolvePromise(newPromise, x, resolve, reject) {
    if (newPromise === x) { // 根据A+规范 如果then中返回当前then的所属promise 应该报类型错误,避免死循环
        return reject(new TypeError(`TypeErr`))
    }
    if (typeof x === 'function' || (typeof x === 'object' && x !== null)) {
        try {
            let then = x.then
            if (typeof then === 'function') { //如果x是一个promise
                then.call(x, y => { //如果promise的结果是成功的,把他往下传 ,如果是失败的就让下一个promise也失败
                    resolvePromise(newPromise, y, resolve, reject) //避免调用resolve的时候传入一个promise
                }, err => {
                    reject(err)                    
                })
            } else { // x是一个普通对象
                resolve(x)
            }
        } catch (e) {
            reject(e)
        }
    } else { // x是一个常量
        resolve(x)
    }
}

处理的规则大致为:

报错直接调用返回的新的promisereject方法

如果上一个promise的值是不为promise的值,直接调用新的promiseresolve方法

然后判断上一个promise的值是不是和新的promise是否相等,想等的话会抛出一个类型错误

然后判断上一个promise的返回值是不是一个promise, 如果是就调用这个promise的方法

# 实现值的穿透

我们经常会看到这些代码




 
Premise.resolve(100).then().then().then(() => {
    console.log(data) // 100
})

这个其实并不复杂,我们来实现以下




 



then(onFullFilled, onRejected) {
    onFullFilled = typeof onFullFilled === 'function' ? onFullFilled : val => val // 实现值的穿透,如果传入的是函数走原来的逻辑,如果不是函数直接返回当前值
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err } // 如果传递的不是函数,则抛出错误让函数接下来捕获
    let newPromise
    ...
}

我们只需要在then的时候对传入的参数进行类型判断就好了,以对onFullFilled的处理为例子,如果不是一个函数,则将val直接返回给下一个then

TIP

其实我们看到的then().catch(e)中的catch,其实就是.then(null, fn)的语法糖,也是利用了值的传递的原理

直接上代码吧




 
catch(cb) {
    return this.then(null, cb)
}

# 实现Promise.resolve

Promise.resolve可以把一个值包装成一个promise,也很简单




 


Promise.resolve = function(value) {
    return new Promise((resolve, rejetc) => {
        resolve(value)
    })
}

# 实现finally

finally的意思是无论如何promise都会走到finally里面去

但是有一点需要注意的是如果finally里面返回一个promise,代码会等待这个promise执行完之后才会往下执行




 








new Promise((resolve, reject) => {
    resolve(1000)
}).finally(() => {
    return new Promise((resolve) => {
            setTimeout(() => {
                resolve()
            }, 3000)
    })
}).then(data => {
    console.log(data)
})

上述代码会在finally理等待3秒,然后再执行后面的then

那我们来简单实现一个finally




 




Promise.prototype.finally = function(cb) {
    return this.then((data) => {
        return Promise.resolve(cb()).then(() => data)
    }, (err) => {
        return Promise.resolve(cb()).then(() => { throw err })
    })
}

主要借助了如果promise里面返回一个promise的话会等着里面的promise执行这个特性,然后也是then的一个语法糖

# 实现Promise.all

先简单看下Promise.all的基础用法吧




 


let fs = require('fs').promises
Promise.all([fs.readFile('./name.txt', 'utf8'), fs.readFile('./age.txt', 'utf8'), 3])
.then((data) => {
    console.log(data)
})

不管是同步代码还是异步代码,promise.all都会等待这个代码执行拿到结果,并且按照数组的顺序返回对应的结果

那我们来简单的实现一下吧




 



























function isPromise(value) {
    if (typeof value === 'function' || (typeof value === 'object' && typeof value !== null)) {
        if (typeof value.then === 'function') {
            return true
        }
    }
    return false
}
Promise.all = function (values) {
    return new Promise((resole, reject) => {
        let arr = []
        let i = 0;
        let processData = (key, value) => {
            arr[key] = value
            if (++i === values.length) {
                resole(arr)
            }
        }
        for (let i = 0; i < values.length; i++) {
            let current = values[i]
            if (isPromise(current)) {
                current.then(y => {
                    processData(i ,y)
                }, reject)
            } else {
                processData(i, current)
            }
        }
    })
}

# 实现Promise.race

Promise.race的用法和all有点像,不过他是如果有一个成功了,就返回这个成功的值,也就是返回最快的值,如果有一个失败了,就返回失败的值

我们直接来实现一下他吧




 









Promise.race = function (values) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < values.length; i++) {
            let current = values[i]
            if (isPromise(current)) {
                current.then(resole, reject)
            } else {
                resolve(current)
            }
        }
    })
}

# 终止promise

在项目中我们经常遇到这样的需求,如果接口超过3秒没有返回,那我们就可以终止这个接口返回,其实是利用了race的特点,我们来模拟一下吧




 


























let p = new Promise((resolve, reject) => { // 模拟接口请求 需要3秒
    setTimeout(() => {
        resolve(123)
    }, 3000)
})

// 现在我们需要在2秒的时候就不等待这个请求的结果了

function wrap(promise) {
    let abort
    let newPromise = new Promise((resolve, reject) => {
        abort = reject
    })
    let p = Promise.race([newPromise, promise])
    p.abort = abort
    return p
}

let p1 = wrap(p);

setTimeout(() => { // 2秒的时候就不等待接口返回的结果了
    p1.abort()
 }, 2000)

p1.then(data => {
    console.log(data)
}).catch((e) => {
    console.log(e)
})

大致意思就是这个请求的结果我不要了,但是请求还是会发出去的