详细解释 JavaScript Promise A+ 规范的完整实现细节,包括 Promise 的状态转换、then 方法的链式调用和错误冒泡机制。

各位好,欢迎来到今天的Promise A+规范深度剖析讲座。我是你们的老朋友,今天咱们要一起扒一扒JavaScript Promise的底裤,哦不,是规范细节。保证让大家看完之后,对Promise的理解更上一层楼,以后面试再也不怕被问Promise了!

准备好了吗?Let’s dive in!

第一部分:Promise的身世之谜——状态转换

Promise,顾名思义,承诺。承诺有三种状态,就像人生一样:

  • Pending (等待中): 这是Promise的初始状态,就像咱们刚开始写代码,还没跑起来呢。
  • Fulfilled (已成功): Promise成功兑现了承诺,就像咱们的代码成功跑通,没bug!
  • Rejected (已失败): Promise未能兑现承诺,就像咱们的代码跑崩了,一堆报错。

这三种状态之间转换是有规则的,不是你想变就变的。

状态 触发条件 下一个状态
Pending Promise刚创建时 Pending, Fulfilled, Rejected
Pending 调用resolve(value),且value不是Promise或thenable对象。 Fulfilled
Pending 调用reject(reason) Rejected
Pending 调用resolve(promise),且promise是另一个Promise实例。 取决于promise的状态
Pending 调用resolve(thenable),且thenable是带有then方法的对象或函数。 取决于thenable的then方法的执行结果
Fulfilled 状态一旦变为Fulfilled,就不能再变为Pending或Rejected。 保持Fulfilled
Rejected 状态一旦变为Rejected,就不能再变为Pending或Fulfilled。 保持Rejected

重点来了:

  • Promise的状态只能从Pending变为FulfilledRejected,且状态一旦改变,就永远定格了,回不去了。 这就像人生没有后悔药。
  • resolve(value) 可以让Promise进入Fulfilled状态,并且把value作为成功的值传递下去。
  • reject(reason) 可以让Promise进入Rejected状态,并且把reason作为失败的原因传递下去。

代码示例:

function myAsyncFunction() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const randomNumber = Math.random();
      if (randomNumber > 0.5) {
        resolve("成功啦!随机数大于0.5");
      } else {
        reject("失败啦!随机数小于等于0.5");
      }
    }, 1000);
  });
}

const myPromise = myAsyncFunction();

console.log("Promise创建后:", myPromise); // 初始状态:Promise { <pending> }

myPromise
  .then(
    (value) => {
      console.log("成功:", value); // 成功状态
      console.log("Promise成功后:", myPromise);
    },
    (reason) => {
      console.log("失败:", reason); // 失败状态
      console.log("Promise失败后:", myPromise);
    }
  );

第二部分:then方法的奇妙之旅——链式调用

then 方法是Promise的核心,它允许咱们在Promise状态改变后执行相应的操作。 它接收两个参数:

  • onFulfilled: Promise成功时执行的回调函数。
  • onRejected: Promise失败时执行的回调函数。

但是!then 方法可不简单,它返回的仍然是一个新的Promise! 这就是实现链式调用的关键。

链式调用的原理:

  1. then 方法返回一个新的Promise,我们称之为promise2
  2. onFulfilledonRejected 的返回值决定了 promise2 的状态:
    • 如果 onFulfilledonRejected 返回一个普通值(非Promise),那么 promise2 会立即变为 Fulfilled 状态,并且将这个返回值作为 promise2 的成功值传递下去。
    • 如果 onFulfilledonRejected 返回一个Promise,那么 promise2 的状态将会和这个返回的Promise的状态保持一致。 如果返回的Promise成功,promise2也成功,并传递成功值; 如果返回的Promise失败,promise2也失败,并传递失败原因。
    • 如果在 onFulfilledonRejected 中抛出一个错误,那么 promise2 会立即变为 Rejected 状态,并且将这个错误作为 promise2 的失败原因传递下去。

代码示例:

function asyncTask(value) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (value > 0) {
        resolve(value * 2);
      } else {
        reject("参数必须大于0");
      }
    }, 500);
  });
}

asyncTask(5)
  .then((result) => {
    console.log("第一次then,结果:", result); // 10
    return result + 5; // 返回普通值
  })
  .then((result) => {
    console.log("第二次then,结果:", result); // 15
    return asyncTask(result); // 返回Promise
  })
  .then((result) => {
    console.log("第三次then,结果:", result); // 30
  })
  .catch((error) => {
    console.error("出错了:", error);
  });

asyncTask(-1)
  .then((result) => {
    console.log("这次不会执行");
  })
  .catch((error) => {
    console.error("出错了:", error); // 参数必须大于0
  });

状态传递流程图:

+-------------+      +-------------+      +-------------+      +-------------+
| initialPromise | --> | promise2      | --> | promise3      | --> | promise4      |
+-------------+      +-------------+      +-------------+      +-------------+
     asyncTask(5)         .then           .then           .then
                        (返回普通值)     (返回Promise)

第三部分:错误处理的艺术——错误冒泡机制

Promise的错误处理机制非常优雅,它采用了类似“冒泡”的方式。 如果在Promise链中,某个环节出错了(Promise变为Rejected状态,或者onFulfilledonRejected中抛出错误),那么这个错误会沿着Promise链向后传递,直到遇到一个catch方法或者下一个onRejected回调函数。

错误冒泡的规则:

  1. 如果 onFulfilledonRejected 中抛出一个错误,那么这个错误会立即传递给下一个 catch 方法。
  2. 如果在 then 方法中没有指定 onRejected 回调函数,那么错误会继续向后传递,直到遇到 catch 方法。
  3. catch 方法本质上也是一个 then 方法,它只接收 onRejected 回调函数作为参数。 也就是说,catch(err => ...) 等价于 then(null, err => ...)
  4. 如果Promise链中没有任何 catch 方法,那么这个错误最终会被抛出到全局环境,导致程序崩溃。

代码示例:

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = Math.random() > 0.5;
      if (success) {
        resolve("数据获取成功");
      } else {
        reject(new Error("数据获取失败"));
      }
    }, 500);
  });
}

fetchData()
  .then((data) => {
    console.log("第一步:", data);
    throw new Error("故意抛出一个错误"); // 模拟错误
  })
  .then((data) => {
    console.log("第二步:", data); // 不会执行
  })
  .catch((error) => {
    console.error("捕获到错误:", error.message); // 捕获到错误: 故意抛出一个错误
    return "错误已处理"; // 返回一个普通值,相当于resolve("错误已处理")
  })
  .then((data) => {
    console.log("错误处理后:", data); // 错误处理后: 错误已处理
  })
  .catch((error) => {
    console.error("这个catch不会执行"); // 上一个catch已经处理了错误
  });

错误冒泡流程图:

+-------------+      +-------------+      +-------------+      +-------------+
| fetchData()   | --> | .then        | --> | .then        | --> | .catch       |
+-------------+      +-------------+      +-------------+      +-------------+
     (可能reject)       (抛出错误)        (不会执行)        (捕获错误)

第四部分:Promise A+规范的实现细节(伪代码)

好了,理论知识讲了一大堆,现在咱们来点硬货,看看如何用代码实现一个符合Promise A+规范的Promise。 这里使用伪代码,重点在于理解实现思路。

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

class MyPromise {
  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((fn) => fn(value));
      }
    };

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

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

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value; // 确保onFulfilled是函数
    onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason; }; // 确保onRejected是函数

    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => { // 确保异步执行,符合A+规范
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.status === REJECTED) {
        setTimeout(() => { // 确保异步执行,符合A+规范
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push((value) => {
          setTimeout(() => { // 确保异步执行,符合A+规范
            try {
              const x = onFulfilled(value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push((reason) => {
          setTimeout(() => { // 确保异步执行,符合A+规范
            try {
              const x = onRejected(reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }

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

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise #<MyPromise>")); // 避免循环引用
  }

  let called; // 防止多次调用resolve或reject

  if (x instanceof MyPromise) {
    // 如果x是Promise,则递归解析
    x.then(
      (y) => {
        resolvePromise(promise2, y, resolve, reject); // 递归调用,直到y不是Promise
      },
      (r) => {
        reject(r);
      }
    );
  } else if (x !== null && (typeof x === "object" || typeof x === "function")) {
    // 如果x是thenable对象或函数
    try {
      const then = x.then; // 取出then方法,可能出错
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject); // 递归调用
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x); // 如果不是函数,直接resolve
      }
    } catch (error) {
      if (called) return;
      called = true;
      reject(error);
    }
  } else {
    // 如果x是普通值
    resolve(x);
  }
}

// 添加resolve和reject静态方法
MyPromise.resolve = function(value) {
    return new MyPromise((resolve) => {
        resolve(value);
    });
};

MyPromise.reject = function(reason) {
    return new MyPromise((resolve, reject) => {
        reject(reason);
    });
};

// 添加all方法
MyPromise.all = function(promises) {
    return new MyPromise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            return reject(new TypeError('Argument must be an array'));
        }

        const results = [];
        let completedCount = 0;

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

        for (let i = 0; i < promises.length; i++) {
            const promise = promises[i];

            Promise.resolve(promise).then((value) => { // 确保数组中的每一项都是Promise
                results[i] = value;
                completedCount++;

                if (completedCount === promises.length) {
                    resolve(results);
                }
            }, (reason) => {
                reject(reason); // 只要有一个promise reject,就reject
            });
        }
    });
};

// 添加race方法
MyPromise.race = function(promises) {
    return new MyPromise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            return reject(new TypeError('Argument must be an array'));
        }

        for (let i = 0; i < promises.length; i++) {
            const promise = promises[i];

            Promise.resolve(promise).then((value) => {
                resolve(value); // 只要有一个promise resolve,就resolve
            }, (reason) => {
                reject(reason); // 只要有一个promise reject,就reject
            });
        }
    });
};

//测试
const promise1 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('Promise 1 resolved');
    }, 1000);
});

const promise2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        reject('Promise 2 rejected');
    }, 500);
});

MyPromise.all([promise1, promise2]).then((values) => {
    console.log('All resolved:', values);
}).catch((reason) => {
    console.log('All rejected:', reason); // 输出:All rejected: Promise 2 rejected
});

MyPromise.race([promise1, promise2]).then((value) => {
    console.log('Race resolved:', value);
}).catch((reason) => {
    console.log('Race rejected:', reason); // 输出:Race rejected: Promise 2 rejected
});

module.exports = MyPromise;

关键点解释:

  • resolvePromise 函数: 这个函数是实现 Promise A+ 规范的核心,它负责处理 onFulfilledonRejected 的返回值 x,并根据 x 的类型来决定 promise2 的状态。 需要处理循环引用、thenable对象等各种情况。
  • 异步执行: 为了符合 Promise A+ 规范,onFulfilledonRejected 必须异步执行,所以使用了 setTimeout
  • 状态不可逆: 一旦 Promise 的状态变为 FULFILLEDREJECTED,就不能再改变。
  • catch 方法: catch 方法只是 then(null, onRejected) 的语法糖。
  • 静态方法resolverejectallrace 这些都是为了方便使用Promise,比如可以快速创建一个已经resolve或者reject的Promise,或者并发处理多个Promise。

第五部分:总结与升华

今天咱们深入探讨了JavaScript Promise A+规范的各个方面,包括状态转换、链式调用、错误冒泡以及实现细节。 希望通过这次讲座,大家对Promise有了更深刻的理解。

几个重要的 takeaway:

  • Promise是一种状态机,状态只能改变一次。
  • then 方法返回新的Promise,实现链式调用。
  • 错误会沿着Promise链冒泡,直到被 catch 捕获。
  • resolvePromise 函数是实现Promise A+规范的关键。

掌握了这些知识,你就可以自信地说: “Promise? 我熟!”

感谢大家的参与,希望今天的讲座对大家有所帮助。 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注