JavaScript Promise 學習記錄(一)

JavaScript Promise 學習記錄(一)

本文首先介紹了promise的三種狀態(tài)及生成不同狀態(tài)promise方法,然后介紹了promise的回調(diào)處理方法then,分析了不同情況下then函數(shù)返回的promise狀態(tài)。 最后通過promise鏈,將以上知識點進行串聯(lián)。希望能夠?qū)Υ蠹矣兴鶐椭S捎谧髡叩哪芰τ邢?,也正在處于學習階段,文中不免會有錯誤,歡迎讀者指正。

Promise的三種狀態(tài)

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

在promise實例上調(diào)用then或catch方法也會生成promise對象,故可進行promise鏈,在下一節(jié)會有所介紹。

tip: chrome瀏覽器的控制臺,是一個快速驗證js代碼的不錯選擇,讀者可以打開chrome瀏覽器,邊閱讀邊實踐。以下代碼皆可直接復制,進行驗證。

首先我們在chrome控制臺中,實踐下生成三種不同狀態(tài)的promise的方法:

//生成pedding狀態(tài)的promise,在控制臺輸入:

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

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

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

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

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

//或是 
Promise.reject(456)
//Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}

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

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}

以上,我們創(chuàng)建了一個Promise對象,該對象創(chuàng)建時,處于pending狀態(tài),我們模擬了一段耗時操作,在5秒后,對該promise進行決議 resolve(123).我們再次查看p時,發(fā)現(xiàn)狀態(tài)已經(jīng)變?yōu)閞esolved。

Promise的回調(diào)

在promise上可以調(diào)用then方法進行回調(diào)的注冊。promise采用了正確與錯誤回調(diào)分流的方式進行分別回調(diào)。區(qū)別于error-first方式。
首先看下error-first方式的回調(diào)處理方法:

var callback = function(error, value) {
   //在回調(diào)中首先判斷是否有錯誤,如果有錯誤,進行處理
   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 采用正常與錯誤結(jié)果分流回調(diào)的處理設(shè)計
var p = new Promise((resolve, reject) => {
    resolve(123)
})

p.then(fullfilled => {
        console.log(fullfilled)  //123
    }, error => {
        console.log(error)   //不會調(diào)用到這里
    }
)

細心的讀者會看到在控制臺除了打印了 123外,還打印了 Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}。還記得我們前面提到then方法也會生成promise嗎,這里打印的就是調(diào)用then方法生成的promise,我們可以先跳過,后面詳細介紹。

以上我們創(chuàng)建了一個promise,并且通過調(diào)用then方法,在該promise注冊了兩個回調(diào)函數(shù)用于監(jiān)聽正確與失敗的處理結(jié)果。由于該promise 通過resolve(123)進行了決議,所以我們在then上注冊的第一個函數(shù),會收到結(jié)果,打印123。
不難想象如果promise做出了拒絕的處理,或者內(nèi)部有錯誤,我們在then上注冊的錯誤處理函數(shù)將被調(diào)用。來看下例子

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

p.then(fullfilled => {
        console.log(fullfilled)  //不會調(diào)用到這里
    }, error => {
        console.log(error)   //456
    }
)

//或者

var p = new Promise((resolve, reject) => {
    foo() //由于未定義foo, 會拋出錯誤
    resolve(123)  // 無法執(zhí)行到
})

p.then(fullfilled => {
        console.log(fullfilled)  //不會調(diào)用這里
    }, error => {
        console.log(error)   //ReferenceError: foo is not defined
    }
)

我們看到在傳遞給promise的匿名函數(shù)中,我們調(diào)用了foo,由于未定義該方法,所以拋出了錯誤。我們可以把拋出錯誤也理解為promise做出了reject決議,并且拒絕的理由是拋出的錯誤信息。

我們看到foo()后面一句,調(diào)用了resolve,由于錯誤的拋出,這行實際沒有被執(zhí)行到。我們來猜測下,如果不是拋出錯誤,而是調(diào)用了reject(456),再調(diào)用resolve(123),resolve會生效嗎?我們注冊的2個回調(diào)都會被執(zhí)行嗎?

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

p.then(fullfilled => {
        console.log(fullfilled)  //不會調(diào)用這里
    }, error => {
        console.log(error)   //456
    }
)

看來是不行,如果是多次調(diào)用resolve呢?

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

p.then(fullfilled => {
        console.log(fullfilled)  //123
    }, error => {
        console.log(error)   //不會調(diào)用這里
    }
)

看來resolve的結(jié)果并沒有被覆寫。
看了上面的例子,引述下書上的結(jié)論吧:promise一旦決議,即由peding狀態(tài)變?yōu)?resolve或reject狀態(tài),其狀態(tài)及值就不會再次改變。這點保證了,后續(xù)在同一個promise上調(diào)用then方法注冊的回調(diào)函數(shù)都將收到相同的結(jié)果。

Promise鏈

上面提到在promise上調(diào)用then方法,會返回新的promise。那新的promise的值和狀態(tài)是什么呢?
答案是取決于執(zhí)行then中注冊的回調(diào)方法時的返回值。

1.無return

相當于調(diào)用了return Promise.resolve(undefined),

Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined} 

也就是我們上文中,調(diào)用then方法時,除了打印回調(diào)中的日志,還打印了promise的原因。

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

比如 return 123。相當于調(diào)用了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,我們是不是可以接著對他調(diào)用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 情況,應(yīng)該是能理解為什么會輸出這三條日志的。

3. return promise or thenable

首先說明下什么是thenable,即有then函數(shù)的對象。

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

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

以上兩例就是thenable對象。我們發(fā)現(xiàn)thenable和promise對象很像。都有then方法。并且then函數(shù) 和 new Promise(function(resolve, reject))中傳遞的匿名函數(shù)很像。

我們看下在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  //先忽略這條

通過以上兩個例子,我們發(fā)現(xiàn)當傳遞thenable時,promise會把thenable對象拆開,根據(jù)thenable對象內(nèi)的then方法,是調(diào)用了第一個回調(diào)函數(shù)(我們以resolve命名,當然也可以是其他),還是第二個回調(diào),來決定如何處理這個thenable對象。是不是很像調(diào)用了 return new Promise(thenable.then)的表現(xiàn),有可能內(nèi)部的實現(xiàn)就是這么處理的。

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

then返回的 promise 情況小結(jié)

  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的默認傳遞

了解了調(diào)用then方法返回的promise的幾種情況后,我們來看下then的默認回調(diào),即少傳或不傳回調(diào),then會如何處理。

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

//傳遞null或少傳參數(shù)時,相當于調(diào)用了
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的默認行為。了解了默認行為后,我們可以簡化調(diào)用方式。當只注冊成功回調(diào)時,可以不傳遞第二個回調(diào)

p1.then(fullfilled=>{
    // 
})

只監(jiān)聽拒絕回調(diào)時,可以使用then的別名方法catch

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

等價于
p1.catch(error => {
    //
})

promise鏈小結(jié)

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

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)
})

總結(jié)

本文首先介紹了promise的三種狀態(tài)及生成不同狀態(tài)promise方法,然后介紹了promise的回調(diào)處理方法then,分析了不同情況下then函數(shù)返回的promise狀態(tài)。最后通過promise鏈,將以上知識點進行串聯(lián)。希望能夠?qū)Υ蠹矣兴鶐椭S捎谧髡叩哪芰τ邢?,也正在處于學習階段,文中不免會有錯誤,歡迎讀者指正。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容