ES6标准引入的异步编程解决方案Promise,能够将层层嵌套的回调转化成扁平的Promise链式调用,优雅地解决了“回调地狱”的问题。
当Promise链中抛出一个错误时,错误信息沿着链路向后传递,直至被捕获。利用这个特性能跳过链中函数的调用,直至链路终点,变相地结束Promise链。
1 Promise.resolve() 2 .then(() => { 3 console.log('[onFulfilled_1]'); 4 throw 'throw on onFulfilled_1'; 5 }) 6 .then(() => { // 中间的函数不会被调用 7 console.log('[onFulfilled_2]'); 8 }) 9 .catch(err => { 10 console.log('[catch]', err); 11 }); 12 // => [onFulfilled_1] 13 // => [catch] throw on onFulfilled_1
然而,若链路中也对错误进行了捕获,则后续的函数可能会继续执行。
1 Promise.resolve() 2 .then(() => { 3 console.log('[onFulfilled_1]'); 4 throw 'throw on onFulfilled_1'; 5 }) 6 .then(() => { 7 console.log('[onFulfilled_2]'); 8 }, err => { // 捕获错误 9 console.log('[onRejected_2]', err); 10 }) 11 .then(() => { // 该函数将被调用 12 console.log('[onFulfilled_3]'); 13 }) 14 .catch(err => { 15 console.log('[catch]', err); 16 }); 17 // => [onFulfilled_1] 18 // => [onRejected_2] throw on onFulfilled_1 19 // => [onFulfilled_3]
解决方案
Promise的then方法接收两个参数:
Promise.prototype.then(onFulfilled, onRejected)
若onFulfilled或onRejected是一个函数,当函数返回一个新Promise对象时,原Promise对象的状态将跟新对象保持一致,详见Promises/A+标准。
因此,当新对象保持“pending”状态时,原Promise链将会中止执行。
1 Promise.resolve() 2 .then(() => { 3 console.log('[onFulfilled_1]'); 4 return new Promise(()=>{}); // 返回“pending”状态的Promise对象 5 }) 6 .then(() => { // 后续的函数不会被调用 7 console.log('[onFulfilled_2]'); 8 }) 9 .catch(err => { 10 console.log('[catch]', err); 11 }); 12 // => [onFulfilled_1]
主要问题是:链式调用时,想在下层返回resolve的情况下,需要在中途得到某种resolve结果就终止调用链。(PS:下层可能是调用其他人编写模块,比如参数不对,它仍会返回resolve,出现错误才会reject,本身下层Promise 返回reject是可以打断调用链的)
下面有个链式调用Promise的测试函数
1 const promiseFun = function(param1){ 2 return new Promise((resolve, reject)=>{ 3 resolve(param1); 4 }); 5 } 6 const promiseTest = function(param1, param2){ 7 return new Promise((resolve, reject)=>{ 8 promiseFun(1).then((number)=>{ 9 console.info(`fun1 result:${number}`); 10 return promiseFun(2); 11 }).then((number)=>{ 12 console.info(`fun2 result:${number}`); 13 return promiseFun(3); 14 }).then((number)=>{ 15 console.info(`fun3 result:${number}`); 16 return promiseFun(4); 17 }).then((number)=>{ 18 console.info(`fun4 result:${number}`); 19 }).catch((err)=>{ 20 console.info(`promiseTest error:${err}`); 21 }); 22 }); 23 } 24 promiseTest('1','2').then((number)=>{ 25 console.info(`promiseTest:${number}`); 26 }).catch((err)=>{ 27 console.info(`promiseTest failed:${err}`); 28 });
现在遇到的一个问题是,比如我们在fun2时,我们调用reject 想终止该链式调用,但实际的结果是仍然会跑到
1 console.info(`fun3 result:${number}`)及console.info(`fun4 result:${number}`)。
PS: 这种想法本身就很奇怪,因为我们调用reject 只是影响promiseTest 下创建那个Promise的状态。也就是调用reject后promiseTest 这个函数会触发onRejected状态。而链式Promise调用状态是由下层Promise对象的状态决定。
1 promiseFun(1).then((number)=>{ 2 console.info(`fun1 result:${number}`); 3 return promiseFun(2); 4 }).then((number)=>{ 5 console.info(`fun2 result:${number}`); 6 if(number === 2){ 7 reject(number) 8 } 9 else{ 10 return promiseFun(3); 11 } 12 }).then((number)=>{ 13 console.info(`fun3 result:${number}`); 14 return promiseFun(4); 15 }).then((number)=>{ 16 console.info(`fun4 result:${number}`); 17 }).catch((err)=>{ 18 console.info(`promiseTest error:${err}`); 19 });
2.2 原因
Promise的then方法接收两个参数:
Promise.prototype.then(onFulfilled, onRejected)
若onFulfilled或onRejected是一个函数,当函数返回一个新Promise对象时,原Promise对象的状态将跟新对象保持一致。
来自:https://promisesaplus.com/
解释下原因:
为什么我们链式调用中reject没有作用?
因为reject仅仅改变的是外层包的promiseTest 返回Promise状态。而链式调用的状态是由promiseFun 返回的状态决定, 而在第二个链式调用then时我们调用reject改变了promiseTest 那种Promise的状态进而使promiseTest触发onRejected状态打印 promiseTest failed:${err},而链式第二个链式调用中本身没做修改链式调用的状态,所以第三个链式继承了第一个链式调用返回的Promise的resolve状态,导致链式调用继续向下运行。
2.3 解决方案
而针对上面的问题,我们想要在resolve的情况下,中断或终止链式调用。
还是基于Promise的特点:原Promise对象的状态将跟新对象保持一致。
我们仅需要在链式调用中,返回一个pending 状态或reject状态的Promise对象即可。后面then 所有resolve(onFulfilled)的处理函数就都不会跑到了。即:
1 return (new Promise((resolve, reject)=>{}));//返回pending状态 2 return (new Promise((resolve, reject)=>{reject()}));//返回reject状态 会被最后catch捕获。
在测试代码中就想这样
then((number)=>{ console.info(`fun2 result:${number}`); if(number === 2){、 return (new Promise((resolve, reject)=>{})); reject(number) } else{ return promiseFun(3); }