then, catch, finally如何影响返回的Promise实例状态

5,216 阅读7分钟

虽然Promise是开发过程中使用非常频繁的一个技术点,但是它的一些细节可能很多人都没有去关注过。我们都知道,.then, .catch, .finally都可以链式调用,其本质上是因为返回了一个新的Promise实例,而这些Promise实例现在的状态是什么或者将来会变成什么状态,很多人心里可能都没个底。我自己也意识到了这一点,于是我通过一些代码试验,发现了一些共性。如果您对这块内容还没有把握,不妨看看。

阅读本文前,您应该对Promise有一些基本认识,比如:

  • Promisepending, fulfilled, rejected三种状态,其决议函数resolve()能将Promise实例的状态由pending转为fulfilled,其决议函数reject()能将Promise实例的状态由pending转为rejected
  • Promise实例的状态一旦转变,不可再逆转。

本文会从一些测验代码入手,看看Promise的几个原型方法在处理Promise状态时的一些细节,最后对它们进行总结归纳,加深理解!

先考虑then的行为

then的语法形式如下:

p.then(onFulfilled[, onRejected]);

onFulfilled可以接受一个value参数,作为Promise状态决议为fulfilled的结果,onRejected可以接受一个reason参数,作为Promise状态决议为rejected的原因。

  • 如果onFulfilledonRejected不返回值,那么.then返回的Promise实例的状态会变成fulfilled,但是伴随fulfilledvalue会是undefined
new Promise((resolve, reject) => {
    resolve(1)
    // reject(2)
}).then(value => {
    console.log('resolution occurred, and the value is: ', value)
}, reason => {
    console.log('rejection occurred, and the reason is: ', reason)
}).then(value => {
    console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
    console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
  • 如果onFulfilledonRejected返回一个值x,那么.then返回的Promise实例的状态会变成fulfilled,并且伴随fulfilledvalue会是x。注意,一个非Promise的普通值在被返回时会被Promise.resolve(x)包装成为一个状态为fulfilledPromise实例。
new Promise((resolve, reject) => {
    reject(2)
}).then(value => {
    console.log('resolution occurred, and the value is: ', value)
}, reason => {
    console.log('rejection occurred, and the reason is: ', reason)
    return 'a new value'
}).then(value => {
    console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
    console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
  • 如果onFulfilledonRejected中抛出一个异常,那么.then返回的Promise实例的状态会变成rejected,并且伴随rejectedreason是刚才抛出的异常的错误对象e
new Promise((resolve, reject) => {
    resolve(1)
}).then(value => {
    console.log('resolution occurred, and the value is: ', value)
    throw new Error('some error occurred.')
}, reason => {
    console.log('rejection occurred, and the reason is: ', reason)
}).then(value => {
    console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
    console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
  • 如果onFulfilledonRejected返回一个Promise实例p2,那么不管p2的状态是什么,.then返回的新Promise实例p1的状态会取决于p2。如果p2现在或将来是fulfilled,那么p1的状态也随之变成fulfilled,并且伴随fulfilledvalue也与p2进行resolve(value)决议时传递的value相同;
new Promise((resolve, reject) => {
    resolve(1)
    // reject(2)
}).then(value => {
    console.log('resolution occurred, and the value is: ', value)
    // return Promise.resolve('a fulfilled promise')
    return Promise.reject('a rejected promise')
}, reason => {
    console.log('rejection occurred, and the reason is: ', reason)
    return Promise.resolve('a fulfilled promise')
    // return Promise.reject('a rejected promise')
}).then(value => {
    console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
    console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})

这个逻辑同样适用于rejected的场景。也就是说,如果p2的状态现在或将来是rejected,那么p1的状态也随之变成rejected,而reason也来源于p1进行reject(reason)决议时传递的reason

new Promise((resolve, reject) => {
    reject(1)
}).then(value => {
    console.log('resolution occurred, and the value is: ', value)
}, reason => {
    console.log('rejection occurred, and the reason is: ', reason)
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('a promise rejected after 3 seconds.')
        }, 3000)
    })
}).then(value => {
    console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
    console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})

再考虑catch的行为

catch的语法形式如下:

p.catch(onRejected);

.catch只会处理rejected的情况,并且也会返回一个新的Promise实例。

.catch(onRejected)then(undefined, onRejected)在表现上是一致的。

事实上,catch(onRejected)从内部调用了then(undefined, onRejected)。

  • 如果.catch(onRejected)onRejected回调中返回了一个状态为rejectedPromise实例,那么.catch返回的Promise实例的状态也将变成rejected
new Promise((resolve, reject) => {
    reject(1)
}).catch(reason => {
    console.log('rejection occurred, and the reason is: ', reason)
    return Promise.reject('rejected')
}).then(value => {
    console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
    console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
  • 如果.catch(onRejected)onRejected回调中抛出了异常,那么.catch返回的Promise实例的状态也将变成rejected
new Promise((resolve, reject) => {
    reject(1)
}).catch(reason => {
    console.log('rejection occurred, and the reason is: ', reason)
    throw 2
}).then(value => {
    console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
    console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
  • 其他情况下,.catch返回的Promise实例的状态将是fulfilled

then, catch 小结

综合以上来看,不管是.then(onFulfilled, onRejected),还是.catch(onRejected),它们返回的Promise实例的状态都取决于回调函数是否抛出异常,以及返回值是什么。

  • 如果回调函数的返回值是一个状态为rejectedPromise实例,那么.then, .catch返回的Promise实例的状态就是rejected

  • 如果回调函数的返回值是一个还未决议的Promise实例p2,那么.then, .catch返回的Promise实例p1的状态取决于p2的决议结果。

  • 如果回调函数中抛出了异常,那么.then, .catch返回的Promise实例的状态就是rejected,并且reason是所抛出异常的对象e

  • 其他情况下,.then, .catch返回的Promise实例的状态将是fulfilled

最后看看finally

不管一个Promise的状态是fulfilled还是rejected,传递到finally方法的回调函数onFinally都会被执行。我们可以把一些公共行为放在onFinally执行,比如把loading状态置为false

注意,onFinally不会接受任何参数,因为它从设计上并不关心Promise实例的状态是什么。

p.finally(function() {
   // settled (fulfilled or rejected)
});

finally方法也会返回一个新的Promise实例,这个新的Promise实例的状态也取决于onFinally的返回值是什么,以及onFinally中是否抛出异常。

你可以通过修改以下代码中的注释部分来验证,不同的返回值对于finally返回的Promise实例的状态的影响。

new Promise((resolve, reject) => {
    reject(1)
}).then(value => {
    console.log('resolution occurred, and the value is: ', value)
}, reason => {
    console.log('rejection occurred, and the reason is: ', reason)
    return Promise.resolve(2);
    // return Promise.reject(3)
}).finally(() => {
    // return Promise.resolve(4)
    // return Promise.reject(5)
    throw new Error('an error')
}).then(value => {
    console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
    console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})

经过测试,可以发现,不管当前Promise的状态是fulfilled还是rejected,只要在onFinally中没有发生以下任何一条情况,finally方法返回的新的Promise实例的状态就会与当前Promise的状态保持一致!这也意味着即使在onFinally中返回一个状态为fulfilledPromise也不能阻止新的Promise实例采纳当前Promise的状态或值!

  • 返回一个状态为或将为rejected的Promise
  • 抛出错误

总的来说,在finally情况下,rejected优先!

如何理解then中抛出异常后会触发随后的catch

由于.then会返回一个新的Promise实例,而在.then回调中抛出了异常,导致这个新Promise的状态变成了rejected,而.catch正是用于处理这个新的Promise实例的rejected场景的。

new Promise((resolve, reject) => {
    resolve(1)
}).then(value => {
    console.log('resolution of the returned promise occurred, and the value is: ', value)
    var a = b; // 未定义b
}).catch(reason => {
    console.log('caught the error occured in the callback of then method, and the reason is: ', reason)
})

最关键一点就是要理解:每次.then, .catch, .finally都产生一个新的Promise实例。

Promise和jQuery的链式调用区别在哪?

上文也提到了,.then, .catch, .finally都产生一个新的Promise实例,所以这种链式调用的对象实例已经发生了变化。可以理解为:

Promise.prototype.then = function() {
  // balabala
  return new Promise((resolve, reject) => {
    // if balabala
    // else if balabala
    // else balabala
  });
}

而jQuery链式调用是基于同一个jQuery实例的,可以简单表述为:

jQuery.fn.css = function() {
  // balabala
  return this;
}

感谢阅读

本文主要是参考了MDN和《你不知道的JavaScript(下卷)》上关于Promise的知识点,简单分析了.then, .catch, .finally中回调函数的不同行为对于三者返回的Promise实例的影响,希望对大家有所帮助。

  • 收藏吃灰不如现在就开始学习,奥利给!
  • 如果您觉得本文有所帮助,请留下您的点赞关注支持一波,谢谢!
  • 快关注公众号前端司南,与笔者一起交流学习吧!