为何不同的Promise.finally赋值方式会导致Promise.catch失效?
我不是JavaScript Promise专家,但遇到了一个怪异问题:我开发了一个基于Axios的Promise工厂API服务,在Vue2组件中调用时,两种不同的finally写法导致了完全不同的结果。
正常工作的代码
服务端代码:
function axiosService() { const execution = // 组装Axios请求方法 const axiosPromise = execution(data) .finally(() => { isFetching = false; }); // 其他无关代码 return { promise: axiosPromise, // 其他内容 }; } export default axiosService;
Vue组件调用代码:
this.axiosPromise = axiosService().promise; this.axiosPromise.catch((e) => { console.log(e); });
导致崩溃的代码
最初的服务端写法,程序会因运行时错误崩溃,要么错误未被捕获,要么Axios生成新错误:
function axiosService() { const execution = // 组装Axios请求方法 const axiosPromise = execution(data); axiosPromise.finally(() => { isFetching = false; }); // 其他无关代码 return { promise: axiosPromise, // 其他内容 }; } export default axiosService;
这种写法是将finally单独附加到axiosPromise变量上,请问我是不是忽略了Promise的某个特性?
问题根源:Promise的不可变性与链式方法的返回值
核心原因是Promise的所有链式方法(包括.finally()、.then()、.catch())都会返回一个全新的Promise实例,而不会修改调用它们的原Promise。
两种写法的差异分析
正常工作的写法:
你把execution(data).finally(...)的返回值(新的Promise实例)赋值给了axiosPromise,并将这个新实例返回给组件。组件里的.catch()绑定在这个包含finally逻辑的新Promise上,不管原Axios请求的错误,还是finally回调中可能出现的错误,都会被这个catch捕获。导致崩溃的写法:
你给原Axios返回的Promise附加了finally,但返回的还是原Promise实例。这里有两个关键问题:- 如果
finally回调中抛出了新错误(比如isFetching未定义、赋值出错等),这个错误会被finally()返回的新Promise承载,但这个新Promise没有被任何catch处理,会直接触发未捕获的Promise rejection,导致程序崩溃。 - 即使
finally本身没有报错,原Promise的错误虽然能被组件的catch捕获,但如果后续有依赖Promise状态的逻辑,会因为你返回的是未包含finally逻辑的原Promise,导致状态不同步(比如isFetching的修改可能没有和Promise状态正确绑定)。
- 如果
简化示例验证
// 情况1:返回带finally的新Promise,错误能被捕获 const p1 = Promise.reject(new Error('请求错误')).finally(() => { console.log('finally执行'); }); p1.catch(e => console.log('捕获到错误:', e.message)); // 输出:捕获到错误:请求错误 // 情况2:返回原Promise,finally抛出的错误未被捕获 const p2 = Promise.reject(new Error('请求错误')); p2.finally(() => { console.log('finally执行'); throw new Error('finally内部错误'); // 这里抛出的错误未被处理 }); p2.catch(e => console.log('捕获到错误:', e.message)); // 原错误被捕获,但finally的错误会触发未捕获异常
总结
你忽略的是Promise的不可变性特性:Promise实例一旦创建,状态就无法修改,所有链式方法都是生成新的Promise来串联后续逻辑。如果要让finally的逻辑和错误处理链路绑定,必须将链式调用的返回值作为最终的Promise向外传递。
内容的提问来源于stack exchange,提问作者Nathaniel Rink

