是 promise chain 解決了回調地獄,而不是隻靠 promise

前言

最近由於對 promise 的狀態依賴理解有誤差,致使在開發過程當中花費了3個小時的時間才發現錯誤。感受浪費時間了,因此結合標準及實踐結果對 promise 的狀態依賴作了一個總結。javascript

問題代碼大體是這樣的:java

// 假設promise爲請求錯誤回調
 let promise = new Promise((resolve, reject) => {
    reject('400'); 
 });
 
 // 統一的響應攔截處理
 promise.then((res) => {
    console.log('promise injector resolved', res) 
 }, (err) => {
    console.log('promise injector rejected', err) 
 })
 // 請求調用處的業務響應處理
 .then((res) => {
    console.log('promise resolved', res) 
 }, (err) => {
    console.log('promise rejected', err) 
 })
 
複製代碼

上面代碼中表現的場景是,錯誤請求通過請求響應攔截器的統一處理後,業務邏輯自己再根據請求狀態來進行一些其餘的處理。原本按照我對promise的理解,這樣是沒有問題的。實際上這裏的問題是,業務中的請求響應處理永遠只會走成功回調,而不會走失敗回調。由於在進行統一的響應攔截處理的時候,就已經丟失了 promise 的狀態了。promise

promise 的三種狀態

promise共有是三種狀態:異步

  • Pending - promise 初始化狀態
  • Fulfilled - 成功
  • Rejected - 失敗

這三種狀態是 Promises/A+ 中描述狀態的術語函數

當咱們建立一個 promise 時,它的狀態是 Pending,而後隨着異步任務的執行,它的狀態必定會變成 Fulfilled 和 Rejected 中的一種,且它的狀態不會再發生任何變化(這一點很重要,後面捋清楚 promise.then() 的返回值就靠這個特性了)。ui

promise.then() 返回的是一個新的 promise

先來一代碼:spa

let promise = Promise.resolve('test');

let thenPromise = promise.then( res =>{
    console.log(res)
});
console.log(promise === thenPromise);
console.log(thenPromise);
複製代碼

運行結果以下: 3d

從運行結果中咱們不可貴出兩點:code

  1. promise.then() 返回的 promise 和調用 .then() 的 promise 不是同一個
  2. promise.then() 返回的 promise 狀態爲 resolved,對應本文中描述 promise 狀態的 Fulfilled。

promise.then() 返回的是一個新的 Promise 的實例對象,而 promise 是可以能表示狀態的,這樣就能夠造成一條狀態的依賴鏈。也就能將多個任務之間的依賴及執行順序表示出來了,從而將異步任務的回調嵌套轉化成爲一條扁平的鏈條。cdn

promise chain

promise chain 說白了就是個 promise 的調用鏈,代碼形式大體以下:

var promise1 = Promise.reject('test');
promise1.then(function(res) {
    console.log('fulfilled1', res)
}, function(err) {
    console.log('rejected1', err)
    return err;
}).then(function(res) {
    console.log('fulfilled2', res)
}, function(err) {
    console.log('rejected2', err)
})
複製代碼

這樣的鏈式調用方式,很好的將多個存在依賴關係的異步任務,將難看回調嵌套轉化成了一條更易於理解的扁平鏈條。從而解決了所謂回調地獄的問題。

promise.then() 回調函數的兩種返回值

當咱們建立了一個 promise 以後,就可使用 promise.then() 來註冊,Fulfilled 和 Rejected 狀態對應的執行函數了。相似下面這樣:

var promise = new Promise((resolve, reject)=>{
    resolve('ok');
});
promise.then((res)=>{
    console.log('fulfilled', res)
}, (err)=>{
    console.log('rejected', err)
})
複製代碼

promise.then() 註冊的回調函數能夠返回不一樣的值,分爲如下兩種:

  • 返回除Promise 實例對象之外的任何值
  • Promise 實例對象

promise.then() 回調函數返回值對 promise chain 的影響

promise.then() 回調函數的返回值決定了其返回的 promise,大體分爲如下兩種情形:

  1. 回調函數返回除 Promise 實例對象以外的任何值,決定返回的 promise 註冊的回調函數的實參
  2. 回調函數返回 Promise 實例對象,決定 promise.then() 返回的 promise 的狀態和其註冊的回調函數的實參

注意: 這裏的回調函數指的是 promise.then() 註冊的回調函數,且是指的是被調用的函數,與是成功回調仍是失敗回調無關

被執行的回調函數返回除 Promise 實例對象以外的任何值

當 promise.then() 註冊的回調函數返回除promise之外的值時,返回的值會被當作 promise.then() 返回的新的 promise 使用 .then() 註冊的回調函數的傳入值。

當回調函數未返回值時,考慮下面代碼:

let promise = Promise.resolve();
promise.then(()=>{
    console.log('promise resolved')
}, ()=>{
    console.log('promise rejected')
})
.then((value)=>{
    console.log('promise resolved1')
    console.log(value)
}, (err)=>{
    console.log('promise rejected1')
    console.log(err)
})

// promise resolved
// promise resolved1
// undefined

let promise1 = Promise.reject();
promise1.then(()=>{
    console.log('promise1 resolved')
}, ()=>{
    console.log('promise1 rejected')
})
.then((value)=>{
    console.log('promise1 resolved1')
    console.log(value)
}, (err)=>{
    console.log('promise1 rejected1')
    console.log(err)
})

// promise1 rejected
// promise1 resolved1
// undefined
複製代碼

運行結果如上,爲了方便閱讀結果,咱們把打印順序調換一下,整理以下

promise 註冊的回調運行了成功回調,然後返回了一個成功狀態的promise,這個返回的 promise 註冊的回調運行了成功回調,回調函數的實參是 undefined;promise1 註冊的回調運行了失敗回調,然後一樣返回了一個成功狀態的 promise ,這個返回的 promise 註冊的回調運行了成功回調,回調函數的實參是 undefined

js 函數執行後默認返回值爲undefined

再來看看返回其餘值的狀況:

let promise = Promise.resolve();
promise.then(()=>{
    console.log('promise resolved')
    return 'value'
}, ()=>{
    console.log('promise rejected')
    return 'err'
})
.then((value)=>{
    console.log('promise resolved1')
    console.log(value)
}, (err)=>{
    console.log('promise rejected1')
    console.log(err)
})
// promise resolved
// promise resolved1
// value

let promise1 = Promise.reject();
promise1.then(()=>{
    console.log('promise1 resolved')
    return 'value'
}, ()=>{
    console.log('promise1 rejected')
    return 'err'
})
.then((value)=>{
    console.log('promise1 resolved1')
    console.log(value)
}, (err)=>{
    console.log('promise1 rejected1')
    console.log(err)
})
// promise1 rejected
// promise1 resolved1
// err
複製代碼

promise.then() 註冊的回調函數,運行的是成功回調,且返回了字符串 「value」,promise.then() 函數返回了一個成功狀態的 promise, 返回的 promise 註冊的回調函數運行了成功回調,回調函數的實參是字符串「value」;promise1.then() 註冊的回調函數,運行的是失敗回調,且返回了字符串 「err」,promise1.then() 函數返回了一個成功狀態的 promise, 返回的 promise 註冊的回調函數運行了成功回調,回調函數的實參是字符串「err」

從上面兩段示例代碼及其運行結果,不可貴出以下結論:

  1. 當 promise.then() 註冊的回調函數返回的不是一個 Promise 示例對象時(代碼只示例了返回字符串和undefined的狀況,其餘狀況有興趣的同窗能夠驗證下返回其餘類型的值),promise.then() 返回的是一個 Fulfilled 狀態的新的 Promise 實例
  2. 這個成功回調的實參就是上一個運行的回調函數的返回值

被執行的回調函數返回 Promise 實例對象

當 promise.then() 註冊的被運行的回調函數返回一個 Promise 實例的時候,回調返回的 Promise 實例的最終的狀態會決定 promise.then() 函數返回的這個新的 promise 的狀態。且promise.then() 函數返回的 promise 註冊的回調函數的實參是由 Promise 實例對象 resolve 或 reject 傳遞的參數決定的。

考慮以下代碼:

let promiseValue = Promise.reject('promiseValue err');

let promise = Promise.resolve();
let promiseThen = promise.then(()=>{
    console.log('promise resolved');
    return promiseValue;
})
promiseThen.then((res)=>{
    console.log('promise resolved1');
    console.log(res)
}, (err)=>{
    console.log('promise rejected1');
    console.log(err);
})

// promise resolved
// promise rejected1
// promiseValue err

console.log(promiseThen === promiseValue)

// false
複製代碼

promise 執行成功狀態回調,回調函數返回失敗狀態的 Promise 實例對象 promiseValue,且其 rejecte 傳入的值是字符串 「promiseValue err」。由於 promiseValue 的狀態是 rejected 因此 promise.then() 返回的新的 Promise 實例(promiseThen === promiseValue 的結果是 false 證實promise.then()返回的)promiseThen 的狀態是 rejected,promiseThen 註冊的失敗回調被調用,打印的實參爲字符串「promiseValue err」。

promise.then() 註冊的被執行的回調函數返回 Promise 實例對象時:

  1. promise.then() 返回一個新的 Promise 實例對象,這個實例對象會等待回調函數返回 Promise 實例對象的狀態變成Fulfilled 或 Rejected 中的一種後返回,且狀態和回調函數返回 Promise 實例對象的狀態一致
  2. promise.then() 返回的 Promise 實例對象,註冊的回調函數被執行的時候接受的實參是由回調函數返回 Promise 實例對象傳遞的結果決定

總結

正是由於 promise.then() 返回的是一個新的 Promise 實例,且這個實例能夠向後傳遞上一個依賴 promise 的異步任務的執行狀態和其要傳遞的數據。這樣的鏈式調用機制,解決了存在依賴關係的異步任務只能在回調函數中不斷嵌套的最後致使代碼難以維護的問題。

相關文章
相關標籤/搜索