你們都知道JavaScript一大特色就是單線程,爲了避免阻塞主線程,有些耗時操做(好比ajax)必須放在任務隊列中異步執行。傳統的異步編程解決方案之一回調,很容易產生臭名昭著的回調地獄問題。html
fs.readdir(source, function(err, files) { if (err) { console.log('Error finding files: ' + err) } else { files.forEach(function(filename, fileIndex) { console.log(filename) gm(source + filename).size(function(err, values) { if (err) { console.log('Error identifying file size: ' + err) } else { console.log(filename + ' : ' + values) aspect = (values.width / values.height) widths.forEach(function(width, widthIndex) { height = Math.round(width / aspect) console.log('resizing ' + filename + 'to ' + height + 'x' + height) this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) { if (err) console.log('Error writing file: ' + err) }) }.bind(this)) } }) }) } })
雖然回調地獄能夠經過減小嵌套、模塊化等方式來解決,但咱們有更好的方案能夠採起,那就是 Promise
git
Promise
是一個對象,保存着異步操做的結果,在異步操做結束後,會變動 Promise
的狀態,而後調用註冊在 then
方法上回調函數。 ES6
原生提供了 Promise
對象,統一用法(具體可參考阮一峯的ES6入門)es6
Promise
的使用想必你們都很熟練,但是究其內部原理,在這以前,我一直是隻知其一;不知其二。本着知其然,也要知其因此然的目的,開始對 Promise
的實現產生了興趣。github
衆所周知,Promise
是對 Promises/A+
規範的一種實現,那咱們首先得了解規範,
詳情請看Promise/A+規範,我的github上有對應的中文翻譯README.mdajax
規範沒有指明如何書寫構造函數,那就參考下 ES6
的構造方式編程
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 異步操做成功 */){ resolve(value); } else { reject(error); } });
Promise
構造函數接受一個函數做爲參數,該函數的兩個參數分別是 resolve
和 reject
數組
resolve
函數的做用是將 Promise
對象的狀態從 pending
變爲 fulfilled
,在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞給註冊在 then
方法上的回調函數(then方法的第一個參數); reject
函數的做用是將 Promise
對象的狀態從 pending
變爲 rejected
,在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞給註冊在 then
方法上的回調函數(then方法的第二個參數)promise
因此咱們要實現的 promise
(小寫以便區分ES6的Promise
)構造函數大致以下:瀏覽器
// promise 構造函數 function promise(fn) { let that = this that.status = 'pending' // 存儲promise的state that.value = '' // 存儲promise的value that.reason = '' // 存儲promise的reason that.onFulfilledCb = [] // 存儲then方法中註冊的回調函數(第一個參數) that.onRejectedCb = [] // 存儲then方法中註冊的回調函數(第二個參數) // 2.1 function resolve(value) { // 將promise的狀態從pending更改成fulfilled,而且以value爲參數依次調用then方法中註冊的回調 setTimeout(() => { if (that.status === 'pending') { that.status = 'fulfilled' that.value = value // 2.2.二、2.2.6 that.onFulfilledCb.map(item => { item(that.value) }) } }, 0) } function reject(reason) { // 將promise的狀態從pending更改成rejected,而且以reason爲參數依次調用then方法中註冊的回調 setTimeout(() => { if (that.status === 'pending') { that.status = 'rejected' that.reason = reason // 2.2.三、2.2.6 that.onRejectedCb.map(item => { item(that.reason) }) } }, 0) } fn(resolve, reject) }
規範2.2.6中明確指明 then
方法能夠被同一個 promise
對象調用,因此這裏須要用一個數組 onFulfilledCb
來存儲then方法中註冊的回調異步
這裏咱們執行 resolve
reject
內部代碼使用setTimeout,是爲了確保 then
方法上註冊的回調能異步執行(規範3.1)
promise
實例具備 then
方法,也就是說,then
方法是定義在原型對象 promise.prototype
上的。它的做用是爲 promise
實例添加狀態改變時的回調函數。
規範2.2
promise
必須提供一個then
方法promise.then(onFulfilled, onRejected)
規範2.2.7then
方法必須返回一個新的promise
閱讀理解規範2.1和2.2,咱們也很容易對then方法進行實現:
promise.prototype.then = function(onFulfilled, onRejected) { let that = this let promise2 // 2.2.一、2.2.5 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v onRejected = typeof onRejected === 'function' ? onRejected : r => r if (that.status === 'pending') { // 2.2.7 return promise2 = new promise((resolve, reject) => { that.onFulfilledCb.push(value => { try { let x = onFulfilled(value) } catch(e) { // 2.2.7.2 reject(e) } }) that.onRejectedCb.push(reason => { try { let x = onRejected(reason) } catch(e) { // 2.2.7.2 reject(e) } }) }) } }
重點在於對 onFulfilled
、 onRejected
函數的返回值x如何處理,規範中提到一個概念叫
Promise Resolution Procedure
,這裏咱們就叫作Promise解決過程
Promise 解決過程是一個抽象的操做,須要輸入一個 promise
和一個值,咱們表示爲 [[Resolve]](promise, x)
,若是 x
有 then
方法且看上去像一個 Promise
,解決程序即嘗試使 promise
接受 x
的狀態;不然用 x
的值來執行 promise
對照規範2.3,咱們再來實現 promise resolution
, promise resolution
針對x的類型作了各類處理:若是 promise
和 x
指向同一對象,以 TypeError
爲 reason
拒絕執行 promise
、若是 x
爲 promise
,則使 promise
接受 x
的狀態、若是 x
爲對象或者函數,判斷 x.then
是不是函數、 若是 x
不爲對象或者函數,以 x
爲參數執行 promise
(resolve和reject參數攜帶promise2的做用域,方便在x狀態變動後去更改promise2的狀態)
// promise resolution function promiseResolution(promise2, x, resolve, reject) { let then let thenCalled = false // 2.3.1 if (promise2 === x) { return reject(new TypeError('promise2 === x is not allowed')) } // 2.3.2 if (x instanceof promise) { x.then(resolve, reject) } // 2.3.3 if (typeof x === 'object' || typeof x === 'function') { try { // 2.3.3.1 then = x.then if (typeof then === 'function') { // 2.3.3.2 then.call(x, function resolvePromise(y) { // 2.3.3.3.3 if (thenCalled) return thenCalled = true // 2.3.3.3.1 return promiseResolution(promise2, y, resolve, reject) }, function rejectPromise(r) { // 2.3.3.3.3 if (thenCalled) return thenCalled = true // 2.3.3.3.2 return reject(r) }) } else { // 2.3.3.4 resolve(x) } } catch(e) { // 2.3.3.3.4.1 if (thenCalled) return thenCalled = true // 2.3.3.2 reject(e) } } else { // 2.3.4 resolve(x) } }
完整代碼可查看stage-4
以上,基本實現了一個簡易版的 promise
,說白了,就是對 Promises/A+
規範的一個翻譯,將規範翻譯成代碼。由於你們的實現都是基於這個規範,因此不一樣的 promise
實現之間可以共存(不得不說制定規範的人才是最厲害的)
function doSomething() { return new promise((resolve, reject) => { setTimeout(() => { resolve('promise done') }, 2000) }) } function doSomethingElse() { return new Promise((resolve, reject) => { setTimeout(() => { reject('ES6 promise') }, 1000) }) } this.promise2 = doSomething().then(doSomethingElse) console.log(this.promise2)
至於 ES6
的 finally
、 all
等經常使用方法,規範雖然沒有制定,可是藉助 then
方法,咱們實現起來也很方便stage-5
ES7
的 Async/Await
也是基於 promise
來實現的,能夠理解成 async
函數會隱式地返回一個 Promise
, await
後面的執行代碼放到 then
方法中
更深層次的思考,你須要理解規範中每一條制定的意義,好比爲何then方法不像jQuery那樣返回this而是要從新返回一個新的promise對象(若是then返回了this,那麼promise2就和promise1的狀態同步,promise1狀態變動後,promise2就沒辦法接受後面異步操做進行的狀態變動)、 promise解決過程
中爲何要規定 promise2
和 x
不能指向同一對象(防止循環引用)
promise完全解決了callback hell,但也存在如下一些問題
then方法每次調用都會建立一個新的promise對象,必定程度上形成了內存的浪費
支持 promise
的庫有不少,如今主流的瀏覽器也都原生支持 promise
了,並且還有更好用的 Async/Await
。之因此還要花精力去寫這篇文章,道理很簡單,就是想對規範有一個更深的理解,但願看到這裏的同窗一樣也能有所收穫