各位好,欢迎来到今天的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
变为Fulfilled
或Rejected
,且状态一旦改变,就永远定格了,回不去了。 这就像人生没有后悔药。 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! 这就是实现链式调用的关键。
链式调用的原理:
then
方法返回一个新的Promise,我们称之为promise2
。onFulfilled
或onRejected
的返回值决定了promise2
的状态:- 如果
onFulfilled
或onRejected
返回一个普通值(非Promise),那么promise2
会立即变为Fulfilled
状态,并且将这个返回值作为promise2
的成功值传递下去。 - 如果
onFulfilled
或onRejected
返回一个Promise,那么promise2
的状态将会和这个返回的Promise的状态保持一致。 如果返回的Promise成功,promise2
也成功,并传递成功值; 如果返回的Promise失败,promise2
也失败,并传递失败原因。 - 如果在
onFulfilled
或onRejected
中抛出一个错误,那么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
状态,或者onFulfilled
或onRejected
中抛出错误),那么这个错误会沿着Promise链向后传递,直到遇到一个catch
方法或者下一个onRejected
回调函数。
错误冒泡的规则:
- 如果
onFulfilled
或onRejected
中抛出一个错误,那么这个错误会立即传递给下一个catch
方法。 - 如果在
then
方法中没有指定onRejected
回调函数,那么错误会继续向后传递,直到遇到catch
方法。 catch
方法本质上也是一个then
方法,它只接收onRejected
回调函数作为参数。 也就是说,catch(err => ...)
等价于then(null, err => ...)
。- 如果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+ 规范的核心,它负责处理onFulfilled
和onRejected
的返回值x
,并根据x
的类型来决定promise2
的状态。 需要处理循环引用、thenable对象等各种情况。- 异步执行: 为了符合 Promise A+ 规范,
onFulfilled
和onRejected
必须异步执行,所以使用了setTimeout
。 - 状态不可逆: 一旦 Promise 的状态变为
FULFILLED
或REJECTED
,就不能再改变。 catch
方法:catch
方法只是then(null, onRejected)
的语法糖。- 静态方法
resolve
、reject
、all
、race
这些都是为了方便使用Promise,比如可以快速创建一个已经resolve或者reject的Promise,或者并发处理多个Promise。
第五部分:总结与升华
今天咱们深入探讨了JavaScript Promise A+规范的各个方面,包括状态转换、链式调用、错误冒泡以及实现细节。 希望通过这次讲座,大家对Promise有了更深刻的理解。
几个重要的 takeaway:
- Promise是一种状态机,状态只能改变一次。
then
方法返回新的Promise,实现链式调用。- 错误会沿着Promise链冒泡,直到被
catch
捕获。 resolvePromise
函数是实现Promise A+规范的关键。
掌握了这些知识,你就可以自信地说: “Promise? 我熟!”
感谢大家的参与,希望今天的讲座对大家有所帮助。 下次再见!