如何在 Promise 链最后的 .then 中访问 orderId 和 payment?
结论
每个 .then() 回调的参数都是局部变量,只能在当前回调的作用域内使用。Promise 链每次只会把前一个回调的返回值传给下一个 .then(),不会自动保存之前出现的 orderId、payment 等值。
如果最后一个 .then() 需要同时访问 orderId、payment 和 remainingBalance,可以在链中持续传递一个包含这些数据的对象。还要注意,不要在链的中间用 .catch() 记录错误后就直接结束。这样会把失败状态转成成功状态,导致后续 .then() 继续执行。
为什么会出现 ReferenceError
下面的 payment 只在当前回调函数内有效:
.then((payment) => {
console.log(`Payment of ${payment} has been received`);
return payment;
})
这和普通函数参数一样:
function callback(payment) {
return payment;
}
离开这个函数后,payment 就不在作用域内了。如果在另一个 .then() 中直接使用它:
.then((remainingBalance) => {
console.log(payment); // ReferenceError
})
JavaScript 无法在当前作用域中找到 payment,于是抛出 ReferenceError: payment is not defined。
return payment 只会让紧接着的下一个 .then() 收到 payment。如果后续回调返回 UpdateWalletBalance(payment),Promise 链中继续传递的值就变成了这个 Promise 的结果,也就是 remainingBalance。Promise 不会替你保存之前的 payment。
推荐做法:在链中传递上下文对象
把需要保留的数据放进同一个对象,并在每一步返回更新后的对象:
const cart = ["shoes", "shirts", "boxers", "pants"];
function createOrder(cart) {
return new Promise((resolve, reject) => {
if (!validateCart(cart)) {
reject(new Error("Cart is not valid"));
return;
}
const orderId = "MK899";
resolve(orderId);
});
}
function proceedToPayment(orderId) {
return new Promise((resolve, reject) => {
if (!orderId) {
reject(new Error(
"Payment has failed. Please try again after some time"
));
return;
}
const payment = "$500";
resolve(payment);
});
}
function updateWalletBalance(payment) {
return new Promise((resolve, reject) => {
if (!payment) {
reject(new Error(
"Balance did not change since payment failed"
));
return;
}
const remainingBalance = "$100";
resolve(remainingBalance);
});
}
function validateCart() {
return true;
}
createOrder(cart)
.then((orderId) => {
console.log(
orderId,
"OrderId verified. Please move on to the payment"
);
return { orderId };
})
.then(({ orderId }) => {
return proceedToPayment(orderId).then((payment) => ({
orderId,
payment
}));
})
.then(({ orderId, payment }) => {
console.log(`Payment of ${payment} has been received`);
return updateWalletBalance(payment).then((remainingBalance) => ({
orderId,
payment,
remainingBalance
}));
})
.then(({ orderId, payment, remainingBalance }) => {
console.log(`Order ID: ${orderId}`);
console.log(`Payment: ${payment}`);
console.log(`Your remaining money is ${remainingBalance}`);
})
.catch((error) => {
console.error(error.message);
});
这样,最后一个 .then() 收到的是:
{
orderId,
payment,
remainingBalance
}
因此它可以同时访问这三个值。
使用 async/await 可以写得更直观
如果没有必须使用 method chaining 的限制,async/await 更适合这种需要按顺序执行,并保留多个中间结果的流程:
async function checkout(cart) {
try {
const orderId = await createOrder(cart);
console.log(
orderId,
"OrderId verified. Please move on to the payment"
);
const payment = await proceedToPayment(orderId);
console.log(`Payment of ${payment} has been received`);
const remainingBalance = await updateWalletBalance(payment);
console.log(`Order ID: ${orderId}`);
console.log(`Payment: ${payment}`);
console.log(`Your remaining money is ${remainingBalance}`);
} catch (error) {
console.error(error.message);
}
}
checkout(cart);
这三个变量都声明在 checkout() 的函数作用域中,后面的代码可以直接访问。
原代码中的其他问题
中间的 .catch() 会吞掉错误
原来的结构类似:
createOrder(cart)
.then(...)
.catch((error) => {
console.log(error.message);
})
.then((orderId) => {
return proceedToPayment(orderId);
});
如果 createOrder() 失败,.catch() 只打印错误,没有 throw,也没有返回一个被拒绝的 Promise。此时 .catch() 返回的 Promise 会以 undefined 成功完成,所以下一个 .then() 仍会执行,并收到 undefined。
通常可以在链的末尾统一处理错误:
createOrder(cart)
.then(...)
.then(...)
.then(...)
.catch((error) => {
console.error(error.message);
});
如果需要在中间记录错误,同时让 Promise 保持拒绝状态,就重新抛出错误:
.catch((error) => {
console.error(error.message);
throw error;
})
reject() 后应立即结束执行
调用 reject() 不会像 return 那样退出 Promise executor:
if (!orderId) {
reject(new Error("Payment failed"));
}
const payment = "$500";
resolve(payment);
Promise 的状态一旦确定,后面的 resolve() 不会再改变结果,但余下的代码仍会执行,可能产生不必要的副作用。应该写成:
if (!orderId) {
reject(new Error("Payment failed"));
return;
}
也可以直接写:
if (!orderId) {
return reject(new Error("Payment failed"));
}
建议统一使用 Error 对象
不要混用字符串和 Error:
reject("Payment failed");
建议改成:
reject(new Error("Payment failed"));
这样,最终的错误处理就能稳定读取 error.message:
.catch((error) => {
console.error(error.message);
});
Promise 链一次只会向后传递一个完成值。需要保留多个中间结果时,可以传递一个聚合对象,也可以改用 async/await,让这些变量处在同一个函数作用域中。
备注:内容仅供参考。
