Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

实现符合A+规范的Promise #16

Open
YIngChenIt opened this issue Jun 30, 2020 · 0 comments
Open

实现符合A+规范的Promise #16

YIngChenIt opened this issue Jun 30, 2020 · 0 comments

Comments

@YIngChenIt
Copy link
Owner

实现符合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)
})

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant