HardenSG / blog

随笔录

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

快速手写一个Promise

HardenSG opened this issue · comments

commented

Promise 作为ES6推出的新特性,解决了前端人员受苦良久的回调地域问题。

由于JS异步函数会被推入到异步队列中,这就意味着:我们想要执行的回调函数往往不是按照我们编写的预期调用顺序所执行。

举个例子我们在Node端向外界暴露一个获取信息的接口 getInfo ,这种情况下往往需要我们获取的用户信息都不是单一的,比如我们需要通过接收到的用户id从数据库中获取到用户的出生地信息,然后利用出生地信息获取用户的出生地人口总量,再利用人口总量计算人均GDP.....

如果下一个获取的信息过度依赖于上一个结果的情况下,这时候就麻烦了!就按我们举的例子来讲,从数据库中取数据的过程本身就是一个异步的过程,那这种情况下,我们不能保证先后顺序。

所以为了确保数据的传递,在ES6以前我们都需要以嵌套回调的形式来实现这种需求

db.query(sql, (err, res) => {
    if(res) {
        // 利用上一个数据,获取下一个目标数据
        db.query(sql, (err, res) => {
            if(res) {
                .....
            }
        })
    }
})

像我们刚刚写的,复杂程度并不高,但是对于常见的项目来讲要获取的信息往往都是非常庞大的,那就意味着我们需要无限层次嵌套回调函数,这也就是所谓的回调地狱。

对于Promise来说,你可以将上述的操作简化为:

const data1 = new Promise((resolve) => {
      db.query(sql, (err, res) => {
          if(res) {
              resolve(res)
          }
      })
})

const data2 = data1.then(res => {
    db.query(sql, (err, res) => {
        if(res) {
            resolve(res)
        }
    })
})

Promise会让你的逻辑变得更加的线性,除此之外,在ES8中被引入了异步方案的最终解决方案 asyncawait,利用的原理就是协程,不是我想表达的内容,暂不表述。

下面的comment来动手实现

commented
class CustomPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'

    constructor(executor) {
        this.PromiseState = CustomPromise.PENDING
        this.PromiseResult = null 
        this.fulfilledCallbacks = []
        this.rejectedCallbacks = []

        try {
            executor(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }

    resolve(value) {
        if(this.PromiseState === CustomPromise.PENDING) {
            this.PromiseState = CustomPromise.FULFILLED
            this.PromiseResult = value 
            this.fulfilledCallbacks.length &&
                executor(this.fulfilledCallbacks)(this.PromiseResult)
        }
    }

    reject(reason) {
        if(this.PromiseState === CustomPromise.PENDING) {
            this.PromiseState = CustomPromise.REJECTED
            this.PromiseResult = reason 

            this.rejectedCallbacks.length && 
                executor(this.rejectedCallbacks)(this.PromiseResult)
        }
    }

    then(onFulfilled, onRejected) {
        const p = new CustomPromise((resolve, reject) => {
            if(this.PromiseState === CustomPromise.PENDING) {
                this.fulfilledCallbacks.push((v) => {
                    queueMicrotask(() => {
                        try {
                            if(!isFunc(onFulfilled)) {
                                return resolve(this.PromiseResult)
                            } else {
                                resolvePromise(
                                    p,
                                    onFulfilled(v),
                                    resolve,
                                    reject
                                )
                            }
                        } catch (error) {
                            reject(error)
                        }
                    })
                })

                this.rejectedCallbacks.push((reason) => {
                    queueMicrotask(() => {
                        try {
                            if(!isFunc(onRejected)) {
                                reject(this.PromiseResult)
                            } else {
                                resolvePromise(
                                    p, 
                                    onRejected(reason),
                                    resolve,
                                    reject
                                )
                            }
                        } catch (error) {
                            reject(error)
                        }
                    })
                })
            }

            if(this.PromiseState === CustomPromise.FULFILLED) {
                queueMicrotask(() => {
                    try {
                        if(!isFunc(onFulfilled)) {
                            return resolve(this.PromiseResult)
                        } else {
                            resolvePromise(
                                p, 
                                onFulfilled(this.PromiseResult),
                                resolve,
                                reject
                            )
                        }
                    } catch (error) {
                        reject(error)
                    }
                })
            }

            if(this.PromiseState === CustomPromise.REJECTED) {
                queueMicrotask(() => {
                    try {
                        if(!isFunc(onFulfilled)) {
                            return resolve(this.PromiseResult)
                        } else {
                            resolvePromise(
                                p, 
                                onRejected(this.PromiseResult),
                                resolve,
                                reject
                            )
                        }
                    } catch (error) {
                        reject(error)
                    }
                })
            }
        })

        return p
    }

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

    finally(cb) {
        this.then(cb, cb)
    }
}

CustomPromise.resolve = function(v) {
    if(isInstanceOf(v, CustomPromise)) {
        return v
    } else if(isObj(v) && 'then' in v) {
        return new CustomPromise((resolve, reject) => {
            v.then(resolve, reject)
        })
    }

    return new CustomPromise((resolve) => {
        resolve(v)
    })
}


CustomPromise.reject = function(v) {
    return new CustomPromise((_, reject) => {
        reject(v)
    })
}

CustomPromise.all = function(iterator) {
    return new CustomPromise((resolve, reject) => {
        if(Array.isArray(iterator)) {
            const result = []
            let count = 0
            if(!(iterator.length)) return resolve([])

            iterator.forEach((v, i) => {
                if(isInstanceOf(v, CustomPromise) || (isObj(v) && 'then' in v)) {
                    CustomPromise.resolve(v).then(
                        y => {
                            count++
                            result[i] = y
                            count === iterator.length && resolve(result)
                        },
                        e => {
                            reject(e)
                        }
                    )
                } else {
                    count++
                    result[i] = v 
                    count === iterator.length && resolve(result)
                }
            })
        } else {
            reject('the iterator is not a [[iterable]] property')
        }
    })
}

CustomPromise.race = function (iterator) {
    return new CustomPromise((resolve, reject) => {
        if (Array.isArray(iterator)) {
            iterator.forEach((item) => {
                CustomPromise.resolve(item).then(resolve, reject);
            });
        } else {
            return reject("cannot read property Symbol(Symbol.iterator)");
        }
    });
};

function isFunc(v) {
    return typeof v === 'function'
}

function isObj(v) {
    return v !== null && typeof v === 'object'
}

function executor(list) {
    return function exec(v) {
        if(!(list instanceof Array)) {
            throw Error('you pass the list is not a Array')
        }

        list.forEach(f => {
            f(v)
        })
    }
}

function isInstanceOf(origin, type) {
    return origin instanceof type
}


function resolvePromise(p, v, resolve, reject) {
    if(p === v) throw Error('The then function can\'t return a instance same to origin instance')
    if(isInstanceOf(v, CustomPromise)) {
        v.then(y => {
            resolvePromise(
                p,
                y,
                resolve,
                reject
            )
        })
    }


    if(v !== null && (isObj(v) || isFunc(v))) {
        try {
            var then = v.then
        } catch (error) {
            reject(error)            
        }

        if(isFunc(then)) {
            let called = false 

            try {
                then.call(
                    v,
                    y => {
                        if(called) return 
                        called = true 
                        resolvePromise(
                            p,
                            v,
                            resolve,
                            reject
                        )
                    },
                    e => {
                        if(called) return 
                        called = true 
                        reject(e) 
                    }
                )
            } catch (error) {
                if(called) return 
                called = true 
                reject(error)
            }
        } else {
            resolve(v)
        }
    } else {
        resolve(v)
    }
}