JavaScript Promise 學習記錄(一)

kkdev163發表於2019-02-26

JavaScript Promise 學習記錄(一)

本文首先介紹了promise的三種狀態及生成不同狀態promise方法,然後介紹了promise的回撥處理方法then,分析了不同情況下then函式返回的promise狀態。最後通過promise鏈,將以上知識點進行串聯。希望能夠對大家有所幫助。由於作者的能力有限,也正在處於學習階段,文中不免會有錯誤,歡迎讀者指正。

Promise的三種狀態

Promise 有三種狀態: 未決議(pending)、已決議(resolved)、已拒絕(rejected)
生成Promise的方式有 new Promise() 、 Promise.resolve() 、Promise.reject()

在promise例項上呼叫then或catch方法也會生成promise物件,故可進行promise鏈,在下一節會有所介紹。

tip: chrome瀏覽器的控制檯,是一個快速驗證js程式碼的不錯選擇,讀者可以開啟chrome瀏覽器,邊閱讀邊實踐。以下程式碼皆可直接複製,進行驗證。

首先我們在chrome控制檯中,實踐下生成三種不同狀態的promise的方法:

//生成pedding狀態的promise,在控制檯輸入:

new Promise ((resolve, reject) => {
    // do nothing
})

//控制檯輸出
//Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}

//生成已決議狀態的promise:
new Promise ((resolve, reject) => {
    resolve(123)
})
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}

//或是 
Promise.resolve(123)
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}

//生成拒絕狀態的promise
new Promise ((resolve, reject) => {
    reject(456)
})
//Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}

//或是 
Promise.reject(456)
//Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}複製程式碼

以上建立的都是立即決議的promise,在我們實際的場景中,Promise往往需要一段時間來處理任務,再完成決議。我們來模擬下這個過程:

var p = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve(123)
    }, 5000)
})複製程式碼

輸入完以上程式碼後,我們立即在控制檯輸入p,檢視p物件

p 
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}複製程式碼

5秒後,我們再次輸入p,

p
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}複製程式碼

以上,我們建立了一個Promise物件,該物件建立時,處於pending狀態,我們模擬了一段耗時操作,在5秒後,對該promise進行決議 resolve(123).我們再次檢視p時,發現狀態已經變為resolved。

Promise的回撥

在promise上可以呼叫then方法進行回撥的註冊。promise採用了正確與錯誤回撥分流的方式進行分別回撥。區別於error-first方式。
首先看下error-first方式的回撥處理方法:

var callback = function(error, value) {
    //在回撥中首先判斷是否有錯誤,如果有錯誤,進行處理
    if (error) { 
        console.log(error)
        return 
    }
    //無錯誤,正常處理
    console.log(`get value :` + value)
}

function foo (key, callback) {
    // do something

    if (key == 1) {
        callback(`key not exit`, null)  //存在錯誤的情況
    } else if (key == 2) {
        callback(null, 3) // 正常處理的情況
    }
}

foo(1, callback) // key not exit
foo(2, callback) // get value :3複製程式碼
  • promise 採用正常與錯誤結果分流回撥的處理設計
var p = new Promise((resolve, reject) => {
    resolve(123)
})

p.then(fullfilled => {
        console.log(fullfilled)  //123
    }, error => {
        console.log(error)   //不會呼叫到這裡
    }
)複製程式碼

細心的讀者會看到在控制檯除了列印了 123外,還列印了 Promise {[[PromiseStatus]]: “resolved”, [[PromiseValue]]: undefined}。還記得我們前面提到then方法也會生成promise嗎,這裡列印的就是呼叫then方法生成的promise,我們可以先跳過,後面詳細介紹。

以上我們建立了一個promise,並且通過呼叫then方法,在該promise註冊了兩個回撥函式用於監聽正確與失敗的處理結果。由於該promise 通過resolve(123)進行了決議,所以我們在then上註冊的第一個函式,會收到結果,列印123。
不難想象如果promise做出了拒絕的處理,或者內部有錯誤,我們在then上註冊的錯誤處理函式將被呼叫。來看下例子

var p = new Promise((resolve, reject) => {
    reject(456)
})

p.then(fullfilled => {
        console.log(fullfilled)  //不會呼叫到這裡
    }, error => {
        console.log(error)   //456
    }
)

//或者

var p = new Promise((resolve, reject) => {
    foo() //由於未定義foo, 會丟擲錯誤
    resolve(123)  // 無法執行到
})

p.then(fullfilled => {
        console.log(fullfilled)  //不會呼叫這裡
    }, error => {
        console.log(error)   //ReferenceError: foo is not defined
    }
)複製程式碼

我們看到在傳遞給promise的匿名函式中,我們呼叫了foo,由於未定義該方法,所以丟擲了錯誤。我們可以把丟擲錯誤也理解為promise做出了reject決議,並且拒絕的理由是丟擲的錯誤資訊。

我們看到foo()後面一句,呼叫了resolve,由於錯誤的丟擲,這行實際沒有被執行到。我們來猜測下,如果不是丟擲錯誤,而是呼叫了reject(456),再呼叫resolve(123),resolve會生效嗎?我們註冊的2個回撥都會被執行嗎?

var p = new Promise((resolve, reject) => {
    reject(456)
    resolve(123)  
})

p.then(fullfilled => {
        console.log(fullfilled)  //不會呼叫這裡
    }, error => {
        console.log(error)   //456
    }
)複製程式碼

看來是不行,如果是多次呼叫resolve呢?

var p = new Promise((resolve, reject) => {
    resolve(123) 
    resolve(456)
})

p.then(fullfilled => {
        console.log(fullfilled)  //123
    }, error => {
        console.log(error)   //不會呼叫這裡
    }
)複製程式碼

看來resolve的結果並沒有被覆寫。
看了上面的例子,引述下書上的結論吧:promise一旦決議,即由peding狀態變為 resolve或reject狀態,其狀態及值就不會再次改變。這點保證了,後續在同一個promise上呼叫then方法註冊的回撥函式都將收到相同的結果。

Promise鏈

上面提到在promise上呼叫then方法,會返回新的promise。那新的promise的值和狀態是什麼呢?
答案是取決於執行then中註冊的回撥方法時的返回值。

1.無return

相當於呼叫了return Promise.resolve(undefined),

Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}複製程式碼

也就是我們上文中,呼叫then方法時,除了列印回撥中的日誌,還列印了promise的原因。

2.return 非promise、非thenable(後面介紹)值。

比如 return 123。相當於呼叫了return Promise.resolve(123), 即返回包裝了決議值為123的promise。

var p1 = Promise.resolve(`p1Value`)
p1.then(fullfilled => {
    console.log(fullfilled)  //p1Value
    return 123
}, error => {
    console.log(error)
})複製程式碼

我們看到控制檯列印了

p1Value
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}複製程式碼

第二行列印的,就是then方法返回的promise例項。既然他返回的是promise,我們是不是可以接著對他呼叫then方法?我們試下

var p1 = Promise.resolve(`p1Value`)
p1.then(fullfilled => {
    console.log(fullfilled)  //p1Value
    return 123
}, error => {
    console.log(error)
}).then(fullfilled => {
    console.log(fullfilled) //123
}, error => {
    console.log(error)
})複製程式碼

我們在控制檯會看到3行輸出

p1Value
123
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}複製程式碼

如果理解了以上兩種return 情況,應該是能理解為什麼會輸出這三條日誌的。

3. return promise or thenable

首先說明下什麼是thenable,即有then函式的物件。

var o = {
    then:function(resolve) {
        resolve(123)
    }
}

var o = {
    then: function(resolve, reject) {
        reject(456)
    }
}複製程式碼

以上兩例就是thenable物件。我們發現thenable和promise物件很像。都有then方法。並且then函式 和 new Promise(function(resolve, reject))中傳遞的匿名函式很像。

我們看下在promise中返回thenabled物件,會是如何?


var o = {
    then:function(resolve) {
        resolve(123)
    }
}

var p1 = Promise.resolve(`p1Value`)
p1.then(fullfilled => {
    console.log(fullfilled)  //p1Value
    return o
}, error => {
    console.log(error)
})複製程式碼

列印的日誌為:

p1Value
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}複製程式碼

再來看個例子

var o = {
    then:function(resolve, reject) {
        reject(456)
    }
}

var p1 = Promise.resolve(`p1Value`)
p1.then(fullfilled => {
    console.log(fullfilled)  //p1Value
    return o
}, error => {
    console.log(error)
})複製程式碼

列印的日誌為:

p1Value
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}
Uncaught (in promise) 456  //先忽略這條複製程式碼

通過以上兩個例子,我們發現當傳遞thenable時,promise會把thenable物件拆開,根據thenable物件內的then方法,是呼叫了第一個回撥函式(我們以resolve命名,當然也可以是其他),還是第二個回撥,來決定如何處理這個thenable物件。是不是很像呼叫了 return new Promise(thenable.then)的表現,有可能內部的實現就是這麼處理的。

還剩下最後一種情況,return promise呢,那就直接返回啦。

then返回的 promise 情況小結

  1. 無return => return Promise.resolve(undefined)
  2. return 123 => return Promise.resolve(123)
  3. return thenable => return new Promise(thenable.then)
  4. return promise => return promise

then的預設值

瞭解了呼叫then方法返回的promise的幾種情況後,我們來看下then的預設回撥,即少傳或不傳回撥,then會如何處理。

var p1 = Promise.resolve(123)
p1.then(null, null)

//傳遞null或少傳引數時,相當於呼叫了
p1.then(fullfilled => {return Promise.resolve(fullfilled)}, error => {return Promise.reject(error) })複製程式碼

即把then所作用的promise的決議值,作為新生成的promise的決議值,再返回這個新的promise。我們來驗證下

var p1 = Promise.resolve(123)

p1.then(null, null)
.then(value => {
    console.log(`onResolve`, value)
  }, error => {
    console.log(`onReject`, error)
  })
//onResolve 123

p1.then(fullfilled => {
    return  Promise.resolve(fullfilled)
},error => {
    return Promise.reject(error) 
})
.then(value => {
    console.log(`onResolve`, value)
  }, error => {
    console.log(`onReject`, error)
  })
//onResolve 123


var p2 = Promise.reject(456)

p2.then(null, null)
.then(value => {
    console.log(`onResolve`, value)
  }, error => {
    console.log(`onReject`, error)
  })
//onReject 456


p2.then(fullfilled => {
    return  Promise.resolve(fullfilled)
},error => {
    return Promise.reject(error) 
})
.then(value => {
    console.log(`onResolve`, value)
  }, error => {
    console.log(`onReject`, error)
  })
//onReject 456複製程式碼

以上驗證了當傳遞null時,then的預設行為。瞭解了預設行為後,我們可以簡化呼叫方式。當只註冊成功回撥時,可以不傳遞第二個回撥

p1.then(fullfilled=>{
    // 
})複製程式碼

只監聽拒絕回撥時,可以使用then的別名方法catch

p1.then(null, error=> {
    //
})

等價於
p1.catch(error => {
    //
})複製程式碼

promise鏈小結

結合以上的promise鏈,隨手寫了個涵蓋以上知識點的例子。大家在終端中檢驗下,看輸出的結果,是否都能理解。

var p1 = Promise.resolve(123)
p1.then(null, null)
.then(fullfiled=>{
    console.log(`onfullfiled`, fullfiled)
    return Promise.resolve(456)
})
.then(fullfiled=> {
    console.log(`onfullfiled`, fullfiled)
    return {
        then: (resolve, reject) => {
            reject(789)
        }
    }
})
.then(null, null)
.then(fullfiled =>{
    console.log(`onfullfiled`, fullfiled)
}, error => {
    console.log(`onerror`, error)
    return Promise.resolve(10)  //將錯誤處理後,又進入了正常處理流
})
.then(fullfiled=> {
    console.log(`onfullfiled`, fullfiled)
    return Promise.reject(`duang duang duang error`)
})
.catch(error => {
    console.log(`onCatch`, error)
})複製程式碼

總結

本文首先介紹了promise的三種狀態及生成不同狀態promise方法,然後介紹了promise的回撥處理方法then,分析了不同情況下then函式返回的promise狀態。最後通過promise鏈,將以上知識點進行串聯。希望能夠對大家有所幫助。由於作者的能力有限,也正在處於學習階段,文中不免會有錯誤,歡迎讀者指正。

相關文章