Promise
做爲ES6極爲重要的一個特性,將咱們從無限的回調地獄中解脫出來,變爲鏈式的編寫回調,大大提升的代碼的可讀性。javascript
使用Promise是極爲簡單的,但只停留在會使用階段仍是會讓咱們不知不覺踩到一些坑的。本文會結合Promise/A+
規範與示例來深刻學習Promise
java
本文較長,例子不少,末尾有一些應用 ^_^node
Promise
做爲是瀏覽器的內置函數對象,除IE不支持外全部主流版本的瀏覽器都是支持的,如不需支持IE能夠在不引入polyfill的狀況下放心食用git
做用es6
js是單線程的,異步是經過Event Loop來實現的,那麼須要一種更加友好的方式來實現咱們的異步操做,而不是無限的回調嵌套github
// no promise callback(() => { callback(() => { callback(() => { ... }) }) }) // with promise new Promise((resolve, reject) => { }).then(() => { }).then(() => { }).then(() => { })
APIapi
先來簡單看一下Promise提供的一些API(注意區分靜態方法與實例方法)數組
構造函數Promisepromise
Promise是一個構造函數,能夠經過new
操做符來建立一個Promise實例瀏覽器
let promise = new Promise((resolve, reject) => { resolve(1) })
靜態方法
實例方法
結合規範與樣例
規範
tips:
狀態
一個Promise實例只能處於pending,fulfilled,rejected
三種狀態中的一種。每次建立的promise實例都會處於pending
狀態,而且只能由pending
變爲fulfilled
或reject
狀態。一旦處於fulfilled
或rejected
狀態不管進行什麼操做都不會再次變動狀態(規範2.1)
// 建立一個處於pending狀態的promise實例 let promise1 = new Promise((resolve, reject) => { }) // 調用resolve()會使promise從pending狀態變爲fulfilled狀態 let promise2 = new Promise((resolve, reject) => { resolve(1) }) // 調用reject()會使promise從pending狀態變爲rejected狀態 let promise3 = new Promise((resolve, reject) => { reject(1) }) // 不管如何更改resolve與reject的執行順序,promise4始終只會處於先調用後轉換的狀態(狀態一旦變爲fulfilled或rejected後不會再次改變) let promise4 = new Promise((resolve, reject) => { resolve(1) reject(1) })
then
參數
實例方法then
的回調函數接受兩個參數,onFulfilled
和onRejected
,都爲可選參數(規範2.2.1)
// onFulfilled會在狀態變爲fulfilled後調用,onFulfilled參數value爲resolve的第一個參數,onRejected同理。(規範2.2.2與2.2.3) let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 1000) }) promise.then((value) => { console.log(value) // 1秒後輸出1 }, (reason) => { }) // 可被同一個promise屢次調用(規範2.2.6) promise.then() promise.then((value) => { console.log(value) // 1秒後輸出1 })
返回值
then
方法必須返回一個promise
實例(規範2.2.7)
假定 promise2 = promise1.then(onFulfilled, onRejected)
var a = new Promise((resolve, reject) => { resolve(1) }) var b = new Promise((resolve, reject) => { reject(1) }) // 若是```onFulfilled```或```onRejected```返回了一個value ```x```,運行promise解決程序(下一節), [[Resolve]](promise2, x) (規範2.2.7.1) var a1 = a.then(function onFulfilled(value) { return 1 }, function onRejected (reason) { }) var b1 = b.then(function onFulfilled(value) { }, function onRejected (reason) { return 1 }) // 若是```onFulfilled```或```onRejected```拋出了一個exception ```e```, ```promise2```必須以e做爲reason rejected (規範2.2.7.2) // 故下方a2 b2 都爲狀態是rejected的promise實例 var a2 = a.then(function onFulfilled(value) { throw Error('test') }, function onRejected (reason) { }) var b2 = b.then(function onFulfilled(value) { }, function onRejected (reason) { throw Error('test') }) // 若是promise1處於fulfilled狀態而且onFulfilled不是一個函數,那麼promise2會以與promise1具備相同value和相同的狀態, 可是與promise1並非同一Promise實例;若爲rejected狀態以此類推 var a3 = a.then() a3 === a // false var b3 = b.then() b3 === b // false
解決程序resolve
在規範中, promise解決程序是一個以promise
和value
做爲輸入的抽象操做,咱們表示爲[[Resolve]](promise, x)
。
能夠認爲在實現裏Promise.resolve()
與new Promise((resolve, reject) => {})
中的resolve
都爲解決程序。規範中x對應實現中resolve的第一個參數,規範中promise在實現中等價於當前promise
能夠理解爲Promise.resolve(x)
與new Promise(resolve) => {resolve(x)}
等價
var c = new Promise((resolve, reject) => { resolve(1) }) var d = new Promise((resolve, reject) => { reject(1) }) var e = new Promise((resolve, reject) => { setTimeout(() => { resolve('5s後resolve') }, 5000) }) // 在返回值章節咱們瞭解到,onFulfilled若是爲函數在不拋出錯誤的狀況下,會調用解決程序處理返回值x // 若是x是promise, 採納他的狀態(注意,但then的返回值並不與x相等)(規範2.3.2) var c1 = Promise.resolve().then(function onFulfilled() { return c }) c1 === c // false // pending狀態下的,只要e狀態變爲,e1也會改變(規範2.3.2.1) var e1 = Promise.resolve().then(function onFulfilled () { return e }) var c2 = Promise.resolve() // 若是promise和x是相同的對象, 使用TypeError做爲reason reject promise(規範2.3.1) var c3 = c2.then(function onFulfilled () { return c3 })
概念:若是一個函數或者對象具備then屬性,那麼叫作thenable(例: { then: function(){} })
// 若是x是thenable 但x.then不是一個函數或者對象,直接返回狀態爲fulfilled,value爲x的promise實例(規範2.3.3.4) var c4 = c1.then(function onFulfilled () { return {} }) var c5 = c1.then(function onFulfilled () { return { then: 2 } }) // 若是x是thenable 而且x.then是一個函數,那麼就會調用這個then方法執行,而且兩個參數resolve與reject擁有改變返回的promise狀態的權利,會按解決程序處理,若是不調用兩個參數方法,則返回的promise爲pending狀態,值得一提的是,調用then方法的時候回以返回對象x做爲then的this綁定調用(規範 2.3.3.3) var c6 = c1.then(function onFulfilled () { return { test: 'test', then: function (resolve, reject) { console.log(this.test) resolve('thenable') } } }) var c7 = c1.then(function onFulfilled () { function x () {} x.test = 'test' x.then = function (resolve, reject) { console.log(this.test) resolve('thenable') } return x }) var c8 = c1.then(function onFulfilled () { return { test: 'test', then: function (resolve, reject) { console.log(this.test) reject('thenable') } } }) // 返回pending狀態的promise var c9 = c1.then(function onFulfilled () { return { then: function () {} } }) // 若是x不是對象也不是函數,則返回狀態爲fulfilled的promise,value爲x var c10 = c1.then(function onFulfilled () { return 'hehe' })
綜上能夠總結幾點
1. ```new Promise, promise.then, Promise.resolve()```都會返回promise實例 2. Promise狀態一旦改變不可再更改 3. ```then```方法返回對象的不一樣會致使不一樣的結果,若是意外返回了thenable有可能會形成意想不到的結果
封裝一個異步請求
var promiseXHR = new Promise((resolve, reject) => { var xhr = new XMLHttpRequest() xhr.open('GET', 'http://www.baidu.com', true) xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { resolve(xhr.response) } else { reject() } } xhr.send(data) })
同時請求按序處理
// 假設有5個章節的數據,咱們須要分別獲取並讀取到c中,利用promise和es6數組api咱們能夠寫出簡潔優雅的代碼完成這個需求 var list = ['Chapter1', 'Chapter2', 'Chapter3', 'Chapter4', 'Chapter5'] var getData = function (key) { return new Promise((resolve, reject) => { // 模擬一些返回時間不相同的異步操做 setTimeout(() => { resolve(key) }, 4000 * Math.random()) }) } var c = '' list.map(i => getData(i)) .reduce((accumulator, current) => { console.log(accumulator) return accumulator.then(() => { return current }).then(value => { c+= value }) }, Promise.resolve(''))
catch
明明在咱們平常工做中經常使用到的catch方法,爲何到如今還一點都沒有提到呢?
由於catch方法就是then方法封裝出來的語法糖而已,由於若是隻想捕獲錯誤,還每次都要書寫then的第一個參數,真的是麻煩至極,如今咱們來寫一個本身的catch
Promise.prototype.mycatch = function (cb) { return this.then(1, function onRejected (reason) { return cb(reason) }) }
// 用到了規範中若是onFulfilled不是一個函數,則忽略,並使用原先promise狀態與value
finally
有的瀏覽器實現了finally,而有的並無,從需求上來說finally只不過是最終要執行一個函數,咱們須要把應該有的狀態或者異常都繼續傳遞,不受其影響。執行的函數與promise的value無任何關係
Promise.prototype.myfinally = function (cb) { return this.then(function onFulfilled (value) { return Promise.resolve(cb()).then(() => value) }, function onRejected (reason) { return Promise.resolve(cb()).then(() => { throw reason }) }) }
經過閱讀規範並寫demo進行驗證測試,對Promise的理解更加深刻了,也能更好的使用它了
若是喜歡能夠star一下,會不斷更新github地址