00后系列-00后手写Promise之道

540 阅读10分钟

Promise修炼秘籍

Promise理论

在手写Promise之前,我们应该先对Promise的每个方法都有一个最基本的认识。

  1. Promise.resolve(value):创建一个已经被解决 (非pending)(可能成功,可能失败)的Promise对象,返回值为value。
  2. Promise.reject(reason):创建一个已经失败的Promise对象,返回值为reason。
  3. Promise.all(iterable):接收一个可迭代对象(比如数组),并且返回一个新的Promise对象,该对象在所有Promise对象都解决后才会解决。如果其中一个Promise对象失败了,该Promise对象将立即失败。
  4. Promise.race(iterable):接收一个可迭代对象(比如数组),并且返回一个新的Promise对象,该对象在第一个Promise对象解决后就会解决。如果第一个Promise对象失败了,该Promise对象将立即失败。
  5. Promise.prototype.then(onFulfilled, onRejected):为Promise对象添加解决(onFulfilled)和拒绝(onRejected)回调函数,并返回一个新的Promise对象。
  6. Promise.prototype.catch(onRejected):为Promise对象添加拒绝回调函数,并返回一个新的Promise对象。
  7. Promise.prototype.finally(onFinally):为Promise对象添加一个回调函数,该回调函数在Promise对象解决或拒绝后都会执行,并返回一个新的Promise对象。
  8. Promise.allSettled(iterable):接收一个可迭代对象(比如数组),并且返回一个新的Promise对象,该对象在所有Promise对象都解决或拒绝后才会解决。该方法返回一个数组,数组中包含每个Promise对象的解决或拒绝结果。
  9. Promise.any(iterable):接收一个可迭代对象(比如数组),并且返回一个新的Promise对象,该对象在至少有一个Promise对象解决后就会解决。如果所有Promise对象都拒绝了,该Promise对象将立即拒绝。该方法返回第一个解决的Promise对象的值。
  10. deferred,是一种基于Promise的编程模式。它是一种将Promise的解决和拒绝函数分离的方法,使得在编写异步代码时更加简单和易于理解。

Promise雏形

Promise,3种状态,pending(等待中),fulfilled(成功|解决),rejected(失败|拒绝)。

PromiseResult

是Promise 被解决或拒绝时 的结果值。

在构造函数中,我们将其初始化为null,表示Promise还没有被解决或拒绝。

当Promise被解决时,我们将其值设为解决值,当Promise被拒绝时,我们将其值设为拒绝原因。

onFulfilledCallbacks,onRejectedCallbacks

这两个变量分别是成功解决和被拒绝时的回调函数列表。当Promise被解决或拒绝时,这些回调函数将会被调用。

Promise构造函数接收一个函数作为参数,该函数被称为执行器函数(executor function)。

执行器函数有两个参数,分别是resolvereject函数,用于将Promise解决或拒绝。

const promise = new Promise((resolve, reject) => {
  const randomNumber = Math.random();

  if (randomNumber > 0.5) {
    resolve('Success');
  } else {
    reject('Failure');
  }
});

promise.then(
  result => console.log(result), // Success
  error => console.error(error) // Failure
);

bind

在JavaScript中,函数的执行上下文由其调用方式决定。当我们将一个函数作为参数传递给另一个函数时,该函数的执行上下文可能会发生改变,导致this关键字指向错误的对象。为了避免这种情况,我们可以使用bind方法将函数绑定到指定的上下文中,以确保在函数执行时this关键字指向正确的对象。

const person = {
  name: 'Alice',
  sayHello() {
    console.log(`Hello, my name is ${this.name}.`);
  }
};

setTimeout(person.sayHello.bind(person), 1000);

在Promise构造函数中,我们需要把bind方法将resolvereject函数绑定到Promise实例上。

这样,当我们在执行器函数中调用resolvereject函数时,它们会在Promise实例的上下文中执行,而不是在全局上下文中执行。这样可以确保this关键字指向正确的对象,从而避免意外的错误。

至此,Promise 最基础的代码结构就可以写好了。

Promise初始版

class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';

    constructor(func) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];
        try {
            func(this.resolve.bind(this), this.reject.bind(this));
        } catch (error) {
            this.reject(error)
        }
    }

    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result;
            this.onFulfilledCallbacks.forEach(callback => {
                callback(result)
            })
        }
    }

    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason;
            this.onRejectedCallbacks.forEach(callback => {
                callback(reason)
            })
        }
    }
}

Promise完善

下面,我们开始逐步实现文章开头的10个方法。

首先,我们先明确 Promise.resolve 这个方法的功能是创建一个已解决的Promise对象,可能因为中英文语言的差异,fulfilled 翻译过来是 成功 | 解决,

所以 很多萌新会认为 Promise.resolve 必定 返回 一个 fulfilled 状态 的 Promise,其实不对,而应该是 返回 一个 非 pending 状态。

其实,中文环境下的 已解决 好像也不一定是成功。比如,购物结束付款这个事,已解决,只是说明我结束了购物结束付款这个事,不一定代表我付款成功了。(无不良引导!)

Promise.resolve

下面说个Promise的冷知识。因为它相对不太常见,而且在实践中很少使用。但是,它对于理解Promise的工作原理和实现方式非常重要。

在Promise规范中,如果一个值是thenable,即它具有then方法,那么这个值可以被视为一个Promise对象。在这种情况下,返回的Promise对象会“跟随”thenable对象,其状态和结果将会与thenable对象的状态和结果保持一致。

值得一谈的是,Promise.resolve(new Error('123')) 返回的是一个状态为fulfilled的Promise。运行以下代码,就可以领悟其中的巧妙之处。

Promise.resolve().then(()=>{
  throw new Error('123');
}).catch((reason)=>{
  console.log('catch',reason);
})

Promise.resolve().then(() => {
  return new Error('456');
}).then((value)=>{
   console.log('then',value);
})

static resolve(value) {
    // 如果这个值是一个 promise ,那么将返回这个 promise 
    if (value instanceof Promise) {
        return value;
    } else if (value instanceof Object && 'then' in value) {
        // 如果这个值是thenable(即带有`"then" `方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
        return new Promise((resolve, reject) => {
            value.then(resolve, reject);
        })
    }
    // 否则返回的promise将以此值完成,即以此值执行`resolve()`方法 (状态为fulfilled)
    return new Promise((resolve) => {
        resolve(value)
    })
}

当你会写Promise.resolve 后,Promise.reject 有手就行。

Promise.reject

返回一个失败的Promise, 入参为 失败的原因 reason

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

仔细对比下,Promise.prototype.then(),Promise.prototype.catch(),Promise.prototype.finally() 这 三个方法功能,其实本质都是一样的,给某一种或两种 非pending状态绑定回调函数,这也是 手写Promise 最 精华(困难)的地方。

Promise.prototype.catch()

结果失败 rejected,执行回调函数

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

Promise.prototype.finally()

无论结果是fulfilled或者是rejected,执行回调函数

finally(callBack) {
    return this.then(callBack, callBack)
}

Promise.prototype.then()

因为then方法中可以传 两个回调函数(虽然我一般只传第一个,另一个用catch代替)分别代表 成功、失败。

需要 根据 PromiseState 状态,走不同的代码逻辑。

如果处于pending状态,就加 成功的回调 push 到 onFulfilledCallbacks,失败的回调 push 到 onRejectedCallbacks。

如果 传入的参数 不是函数的话,直接 返回 promise。

Promise的源码中使用微任务来模拟异步操作,我们手写可以用setTimeout达到异步效果。

then(onFulfilled, onRejected) {
    let promise2 = new myPromise((resolve, reject) => {
        if (this.PromiseState === myPromise.FULFILLED) {
            setTimeout(() => {
                try {
                    if (typeof onFulfilled !== 'function') {
                        resolve(this.PromiseResult);
                    } else {
                        let x = onFulfilled(this.PromiseResult);
                        resolvePromise(promise2, x, resolve, reject);
                    }
                } catch (e) {
                    reject(e);
                }
            });
        } else if (this.PromiseState === myPromise.REJECTED) {
            setTimeout(() => {
                try {
                    if (typeof onRejected !== 'function') {
                        reject(this.PromiseResult);
                    } else {
                        let x = onRejected(this.PromiseResult);
                        resolvePromise(promise2, x, resolve, reject);
                    }
                } catch (e) {
                    reject(e)
                }
            });
        } else if (this.PromiseState === myPromise.PENDING) {
            this.onFulfilledCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        if (typeof onFulfilled !== 'function') {
                            resolve(this.PromiseResult);
                        } else {
                            let x = onFulfilled(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        }
                    } catch (e) {
                        reject(e);
                    }
                });
            });
            this.onRejectedCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        if (typeof onRejected !== 'function') {
                            reject(this.PromiseResult);
                        } else {
                            let x = onRejected(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        }
                    } catch (e) {
                        reject(e);
                    }
                });
            });
        }
    })

    return promise2
}

function resolvePromise(promise2, x, resolve, reject) {
    // 回调函数 返回 的 promise是 自身, 死循环, 异常
    if (x === promise2) {
        throw new TypeError('Chaining cycle detected for promise');
    }

    if (x instanceof myPromise) {
        // 回调结果还是 promise 类型,则 继续 .then ,promise链式调用的原理
        // reject对应的回调函数 直接传下去 ,catch 方法透传的原理。
        x.then(y => {
            resolvePromise(promise2, y, resolve, reject)
        }, reject);
    } else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) {
        try {
            var then = x.then;
        } catch (e) {
            return reject(e);
        }
        //因此,这段代码的作用就是处理 thenable 对象,将它们转换为 Promise 对象,并根据 then 方法的返回值来确定返回的 Promise 对象的状态和值。
        //这样就可以让 thenable 对象也能够被 Promise.prototype.then 方法处理,并进行链式调用。
        if (typeof then === 'function') {
            let called = false;
            try {
                then.call(
                    x,
                    y => {
                        if (called) return;
                        called = true;
                        resolvePromise(promise2, y, resolve, reject);
                    },
                    r => {
                        if (called) return;
                        called = true;
                        reject(r);
                    }
                )
            } catch (e) {
                if (called) return;
                called = true;

                reject(e);
            }
        } else {
            resolve(x);
        }
    } else {
        return resolve(x);
    }
}

至此,手写 Promise 只剩下静态方法部分,其实我认为这些反而简单,因为每一个都是相对独立的,记住功能就能写,都不用记。

Promise.all

成功按顺序拿全部,失败只要头一个失败,局部失败等于整体失败。

 static all(promises) {
        return new Promise((resolve, reject) => {
            if (Array.isArray(promises)) {
                let result = []; 
                let count = 0; 
                if (promises.length === 0) {
                    return resolve(promises);
                }
                promises.forEach((item, index) => {
                    Promise.resolve(item).then(
                        value => {
                            count++;
                            result[index] = value;
                            count === promises.length && resolve(result);
                        },
                        reason => {
                            reject(reason);
                        }
                    )
                })
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }

Promise.race

比谁快,谁快就是谁,成功失败无所谓

static race(promises) {
        return new Promise((resolve, reject) => {
            if (Array.isArray(promises)) {
                if (promises.length > 0) {
                    promises.forEach(item => {
                        Promise.resolve(item).then(resolve, reject);
                    })
                }
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }

Promise.allSettled

不管成功与否,都把结果完整带来

 static allSettled(promises) {
        return new Promise((resolve, reject) => {
            if (Array.isArray(promises)) {
                let result = []; 
                let count = 0; 
                if (promises.length === 0) return resolve(promises);
                promises.forEach((item, index) => {
                    Promise.resolve(item).then(
                        value => {
                            count++;
                            result[index] = {
                                status: 'fulfilled',
                                value
                            }
                            count === promises.length && resolve(result);
                        },
                        reason => {
                            count++;
                            result[index] = {
                                status: 'rejected',
                                reason
                            }
                            count === promises.length && resolve(result);
                        }
                    )
                })
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }

Promise.any

只会记住第一个成功的人,如果全都失败就得记录下所有人的失败原因了。

static any(promises) {
        return new Promise((resolve, reject) => {
            if (Array.isArray(promises)) {
                let errors = []; 
                let count = 0; 
                if (promises.length === 0) return reject(new AggregateError([], 'All promises were rejected'));
                promises.forEach(item => {
                    Promise.resolve(item).then(
                        value => {
                            resolve(value);
                        },
                        reason => {
                            count++;
                            errors.push(reason);
                            count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'));
                        }
                    )
                })
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
 }

Promise.deferred

Promise.deferred = function () {
    let result = {};
    result.promise = new Promise((resolve, reject) => {
        result.resolve = resolve;
        result.reject = reject;
    });
    return result;
}

至此,一个几乎完美的Promise就完成了。说实话,我觉得 thenable 可以不写,知道有这回事就行。

Promise面试手写版

手写 Promise 的主要目的是为了更好地理解 Promise 的工作原理,以及更好地掌握 Promise 的使用技巧,而不是为了完全实现 Promise 的所有功能。

class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';

    constructor(func) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];
        try {
            func(this.resolve.bind(this), this.reject.bind(this));
        } catch (error) {
            this.reject(error)
        }
    }

    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result;
            this.onFulfilledCallbacks.forEach(callback => {
                callback(result)
            })
        }
    }

    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason;
            this.onRejectedCallbacks.forEach(callback => {
                callback(reason)
            })
        }
    }
    then(onFulfilled, onRejected) {
        function resolvePromise(promise2, x, resolve, reject) {
            if (x === promise2) {
                throw new TypeError('Chaining cycle detected for promise');
            }
            if (x instanceof myPromise) {
                x.then(y => {
                    resolvePromise(promise2, y, resolve, reject)
                }, reject);
            } else{
                resolve(x);
            }
        }

        let promise2 = new myPromise((resolve, reject) => {
            if (this.PromiseState === myPromise.FULFILLED) {
                setTimeout(() => {
                    try {
                        if (typeof onFulfilled !== 'function') {
                            resolve(this.PromiseResult);
                        } else {
                            let x = onFulfilled(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        }
                    } catch (e) {
                        reject(e);
                    }
                });
            } else if (this.PromiseState === myPromise.REJECTED) {
                setTimeout(() => {
                    try {
                        if (typeof onRejected !== 'function') {
                            reject(this.PromiseResult);
                        } else {
                            let x = onRejected(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        }
                    } catch (e) {
                        reject(e)
                    }
                });
            } else if (this.PromiseState === myPromise.PENDING) {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            if (typeof onFulfilled !== 'function') {
                                resolve(this.PromiseResult);
                            } else {
                                let x = onFulfilled(this.PromiseResult);
                                resolvePromise(promise2, x, resolve, reject);
                            }
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            if (typeof onRejected !== 'function') {
                                reject(this.PromiseResult);
                            } else {
                                let x = onRejected(this.PromiseResult);
                                resolvePromise(promise2, x, resolve, reject);
                            }
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
            }
        })

        return promise2
    }

    static resolve(value) {
        if (value instanceof myPromise) {
            return value;
        } else{
            return new myPromise((resolve) => {
                resolve(value)
            })
        }
    }

    static reject(reason) {
        return new myPromise((resolve, reject) => {
            reject(reason);
        })
    }

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

    finally(callBack) {
        return this.then(callBack, callBack)
    }

    static all(promises) {
        return new myPromise((resolve, reject) => {
            if (Array.isArray(promises)) {
                let result = []; // 存储结果
                let count = 0; // 计数器
                if (promises.length === 0) {
                    return resolve(promises);
                }

                promises.forEach((item, index) => {
                    myPromise.resolve(item).then(
                        value => {
                            count++;
                            result[index] = value;
                            count === promises.length && resolve(result);
                        },
                        reason => {
                            reject(reason);
                        }
                    )
                })
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }

    static allSettled(promises) {
        return new myPromise((resolve, reject) => {
            if (Array.isArray(promises)) {
                let result = []; // 存储结果
                let count = 0; // 计数器
                if (promises.length === 0) return resolve(promises);
                promises.forEach((item, index) => {
                    myPromise.resolve(item).then(
                        value => {
                            count++;
                            result[index] = {
                                status: 'fulfilled',
                                value
                            }
                            count === promises.length && resolve(result);
                        },
                        reason => {
                            count++;
                            result[index] = {
                                status: 'rejected',
                                reason
                            }
                            count === promises.length && resolve(result);
                        }
                    )
                })
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }
    static any(promises) {
        return new myPromise((resolve, reject) => {
            if (Array.isArray(promises)) {
                let errors = []; // 
                let count = 0; // 计数器

                if (promises.length === 0) return reject(new AggregateError([], 'All promises were rejected'));

                promises.forEach(item => {
                    myPromise.resolve(item).then(
                        value => {
                            resolve(value);
                        },
                        reason => {
                            count++;
                            errors.push(reason);
                            count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'));
                        }
                    )
                })
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }

    static race(promises) {
        return new myPromise((resolve, reject) => {
            if (Array.isArray(promises)) {
                if (promises.length > 0) {
                    promises.forEach(item => {
                        myPromise.resolve(item).then(resolve, reject);
                    })
                }
            } else {
                return reject(new TypeError('Argument is not iterable'))
            }
        })
    }
}

myPromise.deferred = function () {
    let result = {};
    result.promise = new myPromise((resolve, reject) => {
        result.resolve = resolve;
        result.reject = reject;
    });
    return result;
}

参考文章

看不懂的地方,建议看这个,膜拜大佬。 juejin.cn/post/704375…