动态

详情 返回 返回

then, catch, finally如何影響返回的Promise實例狀態 - 动态 详情

雖然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實例的影響,希望對大家有所幫助。

  • 收藏吃灰不如現在就開始學習,奧利給!
  • 如果您覺得本文有所幫助,請留下您的點贊關注支持一波,謝謝!
  • 快關注公眾號程序員白彬,與筆者一起交流學習吧!
user avatar tianmiaogongzuoshi_5ca47d59bef41 头像 toopoo 头像 grewer 头像 cyzf 头像 Leesz 头像 haoqidewukong 头像 zaotalk 头像 yinzhixiaxue 头像 freeman_tian 头像 front_yue 头像 jingdongkeji 头像 kobe_fans_zxc 头像
点赞 267 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.