promise

61 阅读7分钟

Promise

pending(初始状态)、fulfilled(成功状态)和 rejected(失败状态)

Promise 状态 三个状态:pending、fulfilled、reject 两个过程:padding -> fulfilled、padding -> rejected Promise 什么时候会进入 catch 当 pending 为 rejected 时,会进入 catch

实现步骤如下:

  1. 创建 Promise 实例:使用new Promise关键字创建一个新的 Promise 实例,并立即执行一个执行器函数(executor function)。
  2. 执行器函数:执行器函数接收两个参数:resolvereject。这两个函数用于改变 Promise 的状态。
  3. 状态改变
    • 如果执行器函数内的异步操作成功,调用resolve函数,将 Promise 的状态改为fulfilled,并传递一个值给.then()方法的回调函数。
    • 如果执行器函数内的异步操作失败,调用reject函数,将 Promise 的状态改为rejected,并传递一个错误对象给.catch()方法的回调函数。
  4. 回调函数注册.then()方法用于注册成功的回调函数,.catch()方法用于注册失败的回调函数。
  5. 链式调用.then().catch()方法可以返回一个新的 Promise 对象,允许链式调用。

手写 promise

Promise.js

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class CustomPromise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onFulfilledCallbacks.forEach((callback) => callback());
            }
        };

        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach((callback) => callback());
            }
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {
            throw reason;
        };

        const newPromise = new CustomPromise((resolve, reject) => {
            const handleFulfilled = () => {
                try {
                    const result = onFulfilled(this.value);
                    resolvePromise(newPromise, result, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            };

            const handleRejected = () => {
                try {
                    const result = onRejected(this.reason);
                    resolvePromise(newPromise, result, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            };

            if (this.status === FULFILLED) {
                setTimeout(handleFulfilled, 0);
            } else if (this.status === REJECTED) {
                setTimeout(handleRejected, 0);
            } else {
                this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
                this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
            }
        });

        return newPromise;
    }

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

    finally(callback) {
        return this.then(
            (value) => CustomPromise.resolve(callback()).then(() => value),
            (reason) => CustomPromise.resolve(callback()).then(() => {
                throw reason;
            })
        );
    }

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

    static reject(reason) {
        return new CustomPromise((_, reject) => reject(reason));
    }

    static all(promises) {
        return new CustomPromise((resolve, reject) => {
            const results = [];
            let completedCount = 0;

            if (promises.length === 0) {
                resolve(results);
                return;
            }

            const processPromise = (index, promise) => {
                CustomPromise.resolve(promise).then(
                    (value) => {
                        results[index] = value;
                        completedCount++;
                        if (completedCount === promises.length) {
                            resolve(results);
                        }
                    },
                    (reason) => {
                        reject(reason);
                    }
                );
            };

            for (let i = 0; i < promises.length; i++) {
                processPromise(i, promises[i]);
            }
        });
    }

    static race(promises) {
        return new CustomPromise((resolve, reject) => {
            for (let i = 0; i < promises.length; i++) {
                CustomPromise.resolve(promises[i]).then(
                    (value) => {
                        resolve(value);
                    },
                    (reason) => {
                        reject(reason);
                    }
                );
            }
        });
    }
}

function resolvePromise(promise, result, resolve, reject) {
    if (promise === result) {
        return reject(new TypeError('Chaining cycle detected for promise'));
    }
    if (result instanceof CustomPromise) {
        result.then(
            (value) => resolvePromise(promise, value, resolve, reject),
            (reason) => reject(reason)
        );
    } else if (result !== null && (typeof result === 'object' || typeof result === 'function')) {
        let then;
        try {
            then = result.then;
        } catch (error) {
            return reject(error);
        }
        if (typeof then === 'function') {
            let called = false;
            try {
                then.call(
                    result,
                    (value) => {
                        if (called) return;
                        called = true;
                        resolvePromise(promise, value, resolve, reject);
                    },
                    (reason) => {
                        if (called) return;
                        called = true;
                        reject(reason);
                    }
                );
            } catch (error) {
                if (called) return;
                reject(error);
            }
        } else {
            resolve(result);
        }
    } else {
        resolve(result);
    }
}
  1. 构造函数:初始化 Promise 的状态为 pending,并定义 resolve 和 reject 方法来改变状态。执行 executor 函数时,若出现异常则调用 reject
  2. then 方法:返回一个新的 Promise,根据当前 Promise 的状态处理 onFulfilled 和 onRejected 回调,并使用 resolvePromise 处理结果。
  3. catch 方法:调用 then 方法,只传入 onRejected 回调。
  4. finally 方法:无论 Promise 状态如何,都会执行 callback,并返回一个新的 Promise
  5. resolve 静态方法:如果传入的是 CustomPromise 实例则直接返回,否则创建一个已解决的 Promise
  6. reject 静态方法:创建一个已拒绝的 Promise
  7. all 静态方法:接收一个 Promise 数组,当所有 Promise 都成功时,返回包含所有结果的数组;若有一个失败,则立即拒绝。
  8. race 静态方法:接收一个 Promise 数组,哪个 Promise 率先改变状态,就以该状态和结果解决返回的 Promise
  9. resolvePromise 函数:处理 then 方法返回值的解析逻辑,避免循环引用等问题。

使用

// 测试自定义 Promise
const promise1 = new CustomPromise((resolve) => {
    setTimeout(() => {
        resolve('Promise 1 resolved');
    }, 1000);
});

const promise2 = new CustomPromise((resolve) => {
    setTimeout(() => {
        resolve('Promise 2 resolved');
    }, 2000);
});

CustomPromise.all([promise1, promise2]).then((results) => {
    console.log(results);
}).catch((error) => {
    console.error(error);
});

手写 promise.race

Promise.race 是 JavaScript 中 Promise 对象的一个静态方法,它接收一个可迭代对象(通常是数组)作为参数,该数组中的每个元素都是一个 Promise 实例。Promise.race 会返回一个新的 Promise,这个新 Promise 会在传入的 Promise 数组中任意一个 Promise 率先改变状态(成功或失败)时,以相同的状态和结果进行解决。 PromiseRace.js

function customPromiseRace(promises) {
    return new Promise((resolve, reject) => {
        // 遍历传入的可迭代对象中的每个 Promise
        for (let i = 0; i < promises.length; i++) {
            // 使用 Promise.resolve 将元素转换为 Promise 对象
            Promise.resolve(promises[i])
              .then((value) => {
                    // 若有 Promise 成功,立即将结果传递给新 Promise 的 resolve 函数
                    resolve(value);
                })
              .catch((error) => {
                    // 若有 Promise 失败,立即将错误传递给新 Promise 的 reject 函数
                    reject(error);
                });
        }
    });
}
  1. 返回新 PromisecustomPromiseRace 函数返回一个新的 Promise 实例,这个实例会根据传入数组中率先完成的 Promise 来改变自身状态。
  2. 遍历 Promise 数组:使用 for 循环遍历传入的 promises 数组。
  3. 处理每个 Promise:对数组中的每个元素使用 Promise.resolve 进行包装,确保它是一个 Promise 对象。然后为其添加 thencatch 方法。
    • 如果某个 Promise 成功(状态变为 fulfilled),就调用 resolve 函数,将结果传递给新 Promise 实例,使其状态变为 fulfilled
    • 如果某个 Promise 失败(状态变为 rejected),就调用 reject 函数,将错误传递给新 Promise 实例,使其状态变为 rejected

使用

const promise1 = new Promise((resolve) => setTimeout(() => resolve('Promise 1 完成'), 1000));
const promise2 = new Promise((resolve) => setTimeout(() => resolve('Promise 2 完成'), 500));

customPromiseRace([promise1, promise2]).then((result) => {
    console.log(result); 
}).catch((error) => {
    console.error(error);
});

在这个示例中,promise2 会先完成,所以 customPromiseRace 返回的 Promise 会以 promise2 的结果进行解决,最终控制台会输出 Promise 2 完成

手写 promise.all

Promise.all 是 JavaScript 中 Promise 对象的一个静态方法,它接收一个可迭代对象(通常是数组)作为参数,数组里每个元素都是一个 Promise 实例。该方法会返回一个新的 Promise,当传入的所有 Promise 都成功(状态变为 fulfilled)时,新 Promise 才会成功,其结果是一个包含所有 Promise 结果的数组;若其中任何一个 Promise 失败(状态变为 rejected),新 Promise 会立即失败,结果为第一个失败 Promise 的错误信息。 PromiseAll.js

function customPromiseAll(promises) {
    return new Promise((resolve, reject) => {
        // 用于存储每个 Promise 的结果
        const results = [];
        // 记录已经完成的 Promise 数量
        let completedCount = 0;

        // 如果传入的数组为空,直接成功返回空数组
        if (promises.length === 0) {
            resolve(results);
            return;
        }

        // 遍历传入的每个 Promise
        for (let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i])
              .then((value) => {
                    // 将当前 Promise 的结果存入对应的位置
                    results[i] = value;
                    // 完成数量加 1
                    completedCount++;
                    // 当所有 Promise 都完成时,新 Promise 成功
                    if (completedCount === promises.length) {
                        resolve(results);
                    }
                })
              .catch((error) => {
                    // 若有任何一个 Promise 失败,新 Promise 立即失败
                    reject(error);
                });
        }
    });
}

代码解释

  1. 返回新 PromisecustomPromiseAll 函数返回一个新的 Promise 实例,它会根据传入数组中所有 Promise 的完成情况来改变自身状态。
  2. 初始化变量
    • results 数组用于存储每个 Promise 的结果,其索引和传入数组中 Promise 的索引对应。
    • completedCount 用于记录已经完成的 Promise 数量。
  3. 处理空数组情况:若传入的数组为空,直接调用 resolve 函数,使新 Promise 成功,结果为空数组。
  4. 遍历 Promise 数组:使用 for 循环遍历传入的 promises 数组。
  5. 处理每个 Promise
    • 使用 Promise.resolve 确保每个元素都是 Promise 对象。
    • 为每个 Promise 添加 thencatch 方法。
      • Promise 成功,将结果存入 results 数组对应位置,completedCount 加 1。当 completedCount 等于数组长度时,说明所有 Promise 都已成功,调用 resolve 函数使新 Promise 成功,结果为 results 数组。
      • Promise 失败,调用 reject 函数使新 Promise 立即失败,结果为该 Promise 的错误信息。

使用示例

const promise1 = new Promise((resolve) => setTimeout(() => resolve('Promise 1 完成'), 1000));
const promise2 = new Promise((resolve) => setTimeout(() => resolve('Promise 2 完成'), 2000));

customPromiseAll([promise1, promise2]).then((results) => {
    console.log(results); 
}).catch((error) => {
    console.error(error);
});

在这个示例中,只有当 promise1promise2 都成功完成后,customPromiseAll 返回的 Promise 才会成功,最终控制台会输出 ['Promise 1 完成', 'Promise 2 完成']

如果有 100 个异步请求,如何使用 Promise 控制