觀感度:🌟🌟🌟🌟🌟前端
口味:海底撈git
烹飪時間:15mingithub
本文已收錄在前端食堂同名倉庫Github github.com/Geekhyt,歡迎光臨食堂,若是以爲酒菜還算可口,賞個 Star 對食堂老闆來講是莫大的鼓勵。
Promise/A+ 規範鎮樓!web
若是你沒讀過 Promise/A+ 規範也不要緊,我幫你總結了以下三部分重點:segmentfault
不過建議看完本文後仍是要親自去讀一讀,很少 bb,開始展現。設計模式
Promise 的三個狀態分別是 pending
、fulfilled
和 rejected
。數組
pending
: 待定,Promise 的初始狀態。在此狀態下能夠落定 (settled)
爲 fulfilled
或 rejected
狀態。fulfilled
: 兌現(解決),表示執行成功。Promise 被 resolve 後的狀態,狀態不可再改變,且有一個私有的值 value。rejected
: 拒絕,表示執行失敗。Promise 被 reject 後的狀態,狀態不可再改變,且有一個私有的緣由 reason。注意:value 和 reason 也是不可變的,它們包含原始值或對象的不可修改的引用,默認值爲 undefined
。promise
要求必須提供一個 then 方法來訪問當前或最終的 value 或 reason。安全
promise.then(onFulfilled, onRejected)
1.then 方法接受兩個函數做爲參數,且參數可選。
2.若是可選參數不爲函數時會被忽略。
3.兩個函數都是異步執行,會放入事件隊列等待下一輪 tick。
4.當調用 onFulfilled 函數時,會將當前 Promise 的 value 值做爲參數傳入。
5.當調用 onRejected 函數時,會將當前 Promise 的 reason 失敗緣由做爲參數傳入。
6.then 函數的返回值爲 Promise。
7.then 能夠被同一個 Promise 屢次調用。
Promise 的解決過程是一個抽象操做,接收一個 Promise 和一個值 x。babel
針對 x 的不一樣值處理如下幾種狀況:
1.x 等於 Promise
拋出 TypeError 錯誤,拒絕 Promise。
2.x 是 Promise 的實例
若是 x 處於待定狀態,那麼 Promise 繼續等待直到 x 兌現或拒絕,不然根據 x 的狀態兌現/拒絕 Promise。
3.x 是對象或函數
取出 x.then 並調用,調用時將 this 指向 x。將 then 回調函數中獲得的結果 y 傳入新的 Promise 解決過程當中,遞歸調用。
若是執行報錯,則將以對應的失敗緣由拒絕 Promise。
這種狀況就是處理擁有 then() 函數的對象或函數,咱們也叫它 thenable
。
4.若是 x 不是對象或函數
以 x 做爲值執行 Promise。
var PENDING = 'pending'; var FULFILLED = 'fulfilled'; var REJECTED = 'rejected';
建立 Promise 時須要傳入 execute
回調函數,接收兩個參數,這兩個參數分別用來兌現和拒絕當前 Promise。
因此咱們須要定義 resolve()
和 reject()
函數。
初始狀態爲 PENDING
,在執行時可能會有返回值 value
,在拒絕時會有拒絕緣由 reason
。
同時須要注意,Promise 內部的異常不能直接拋出,須要進行異常捕獲。
function Promise(execute) { var that = this; that.state = PENDING; function resolve(value) { if (that.state === PENDING) { that.state = FULFILLED; that.value = value; } } function reject(reason) { if (that.state === PENDING) { that.state = REJECTED; that.reason = reason; } } try { execute(resolve, reject); } catch (e) { reject(e); } }
then
方法用來註冊當前 Promise 狀態落定後的回調,每一個 Promise 實例都須要有它,顯然要寫到 Promise 的原型 prototype
上,而且 then() 函數接收兩個回調函數做爲參數,分別是 onFulfilled
和 onRejected
。
Promise.prototype.then = function(onFulfilled, onRejected) {}
根據上面第 2 條規則,若是可選參數不爲函數時應該被忽略,咱們要對參數進行以下判斷。
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(x) { return x; } onRejected = typeof onRejected === 'function' ? onRejected : function(e) { throw e; }
根據第 3 條規則,須要使用 setTimeout
延遲執行,模擬異步。
根據第 4 條、第 5 條規則,須要根據 Promise 的狀態來執行對應的回調函數。
在 PENDING 狀態時,須要等到狀態落定才能調用。咱們能夠將 onFulfilled 和 onRejected 函數存到 Promise 的屬性 onFulfilledFn
和 onRejectedFn
中,
當狀態改變時分別調用它們。
var that = this; var promise; if (that.state === FULFILLED) { setTimeout(function() { onFulfilled(that.value); }); } if (that.state === REJECTED) { setTimeout(function() { onRejected(that.reason); }); } if (that.state === PENDING) { that.onFulfilledFn = function() { onFulfilled(that.value); } that.onRejectedFn = function() { onRejected(that.reason); } }
根據第 6 條規則,then 函數的返回值爲 Promise,咱們分別給每一個邏輯添加並返回一個 Promise。
同時,then 支持鏈式調用,咱們須要將 onFulfilledFn 和 onRejectedFn 改爲數組。
var that = this; var promise; if (that.state === FULFILLED) { promise = new Promise(function(resolve, reject) { setTimeout(function() { try { onFulfilled(that.value); } catch (reason) { reject(reason); } }); }); } if (that.state === REJECTED) { promise = new Promise(function(resolve, reject) { setTimeout(function() { try { onRejected(that.reason); } catch (reason) { reject(reason); } }); }); } if (that.state === PENDING) { promise = new Promise(function(resolve, reject) { that.onFulfilledFn.push(function() { try { onFulfilled(that.value); } catch (reason) { reject(reason); } }) that.onRejectedFn.push(function() { try { onRejected(that.reason); } catch (reason) { reject(reason); } }); }); }
與上面相對應的,再將 Promise 的構造函數相應的進行改造。
1.添加 onFulFilledFn 和 onRejectedFn 數組。
2.resolve() 和 reject() 函數改變狀態時,須要異步調用數組中的函數,一樣使用 setTimeout 來模擬異步。
function Promise(execute) { var that = this; that.state = PENDING; that.onFulfilledFn = []; that.onRejectedFn = []; function resolve(value) { setTimeout(function() { if (that.state === PENDING) { that.state = FULFILLED; that.value = value; that.onFulfilledFn.forEach(function(fn) { fn(that.value); }) } }) } function reject(reason) { setTimeout(function() { if (that.state === PENDING) { that.state = REJECTED; that.reason = reason; that.onRejectedFn.forEach(function(fn) { fn(that.reason); }) } }) } try { execute(resolve, reject); } catch (e) { reject(e); } }
Promise 解決過程分爲如下幾種狀況,咱們須要分別進行處理:
1.x 等於 Promise TypeError 錯誤
此時至關於 Promise.then 以後 return 了本身,由於 then 會等待 return 後的 Promise,致使本身等待本身,一直處於等待。。
function resolvePromise(promise, x) { if (promise === x) { return reject(new TypeError('x 不能等於 promise')); } }
2.x 是 Promise 的實例
若是 x 處於待定狀態,Promise 會繼續等待直到 x 兌現或拒絕,不然根據 x 的狀態兌現/拒絕 Promise。
咱們須要調用 Promise 在構造時的函數 resolve() 和 reject() 來改變 Promise 的狀態。
function resolvePromise(promise, x, resolve, reject) { // ... if (x instanceof Promise) { if (x.state === FULFILLED) { resolve(x.value); } else if (x.state === REJECTED) { reject(x.reason); } else { x.then(function(y) { resolvePromise(promise, y, resolve, reject); }, reject); } } }
3.x 是對象或函數
取出 x.then 並調用,調用時將 this 指向 x,將 then 回調函數中獲得的結果 y 傳入新的 Promise 解決過程當中,遞歸調用。
若是執行報錯,則將以對應的失敗緣由拒絕 Promise。
x 多是一個 thenable 而非真正的 Promise。
須要設置一個變量 executed
避免重複調用。
function resolvePromise(promise, x, resolve, reject) { // ... if ((x !== null) && ((typeof x === 'object' || (typeof x === 'function'))) { var executed; try { var then = x.then; if (typeof then === 'function') { then.call(x, function(y) { if (executed) return; executed = true; return resolvePromise(promise, y, resolve, reject); }, function (e) { if (executed) return; executed = true; reject(e); }) } else { resolve(x); } } catch (e) { if (executed) return; executed = true; reject(e); } } }
4.直接將 x 做爲值執行
function resolvePromise(promise, x, resolve, reject) { // ... resolve(x) }
// 爲了支持測試,將模塊導出 module.exports = { deferred() { var resolve; var reject; var promise = new Promise(function (res, rej) { resolve = res; reject = rej; }) return { promise, resolve, reject } } }
咱們能夠選用這款測試工具對咱們寫的 Promise 進行測試 Promise/A+ 測試工具: promises-aplus-tests。
目前支持 827 個測試用例,咱們只須要在導出模塊的時候遵循 CommonJS 規範,按照要求導出對應的函數便可。
Promise.resolve()
能夠實例化一個解決(fulfilled) 的 Promise。
Promise.resolve = function(value) { if (value instanceof Promise) { return value; } return new Promise(function(resolve, reject) { resolve(value); }); }
Promise.reject()
能夠實例化一個 rejected 的 Promise 並拋出一個異步錯誤(這個錯誤不能經過try/catch捕獲,只能經過拒絕處理程序捕獲)
Promise.reject = function(reason) { return new Promise(function(resolve, reject) { reject(reason); }); }
Promise.prototype.catch()
方法用於給 Promise 添加拒絕時的回調函數。
Promise.prototype.catch = function(onRejected) { return this.then(null, onRejected); }
Promise.prototype.finally()
方法用於給 Promise 添加一個無論最終狀態如何都會執行的操做。
Promise.prototype.finally = function(fn) { return this.then(function(value) { return Promise.resolve(value).then(function() { return value; }); }, function(error) { return Promise.resolve(reason).then(function() { throw error; }); }); }
Promise.all()
方法會將多個 Promise 實例組合成一個新的 Promise 實例。
組合後的 Promise 實例只有當每一個包含的 Promise 實例都解決(fulfilled)後才解決(fulfilled),若是有一個包含的 Promise 實例拒絕(rejected)了,則合成的 Promise 也會拒絕(rejected)。
兩個注意點:
Promise.all = function(promiseArr) { return new Promise(function(resolve, reject) { const length = promiseArr.length; const result = []; let count = 0; if (length === 0) { return resolve(result); } for (let item of promiseArr) { Promise.resolve(item).then(function(data) { result[count++] = data; if (count === length) { resolve(result); } }, function(reason) { reject(reason); }); } }); }
Promise.race()
一樣返回一個合成的 Promise 實例,其會返回這一組中最早解決(fulfilled)或拒絕(rejected)的 Promise 實例的返回值。
Promise.race = function(promiseArr) { return new Promise(function(resolve, reject) { const length = promiseArr.length; if (length === 0) { return resolve(); } for (let item of promiseArr) { Promise.resolve(item).then(function(value) { return resolve(value); }, function(reason) { return reject(reason); }); } }); }
Promise.any() 至關於 Promise.all() 的反向操做
,一樣返回一個合成的 Promise 實例,只要其中包含的任何一個 Promise 實例解決(fulfilled)了,合成的 Promise 就解決(fulfilled)。
只有當每一個包含的 Promise 都拒絕(rejected)了,合成的 Promise 才拒絕(rejected)。
Promise.any = function(promiseArr) { return new Promise(function(resolve, reject) { const length = promiseArr.length; const result = []; let count = 0; if (length === 0) { return resolve(result); } for (let item of promiseArr) { Promise.resolve(item).then((value) => { return resolve(value); }, (reason) => { result[count++] = reason; if (count === length) { reject(result); } }); } }); }
Promise.allSettled()
方法也是返回一個合成的 Promise,不過只有等到全部包含的每一個 Promise 實例都返回結果落定時,不論是解決(fulfilled)仍是拒絕(rejected),合成的 Promise 纔會結束。一旦結束,狀態老是 fulfilled。
其返回的是一個對象數組,每一個對象表示對應的 Promise 結果。
對於每一個結果對象,都有一個 status 字符串。若是它的值爲 fulfilled,則結果對象上存在一個 value 。若是值爲 rejected,則存在一個 reason 。
Promise.allSettled = function(promiseArr) { return new Promise(function(resolve) { const length = promiseArr.length; const result = []; let count = 0; if (length === 0) { return resolve(result); } else { for (let item of promiseArr) { Promise.resolve(item).then((value) => { result[count++] = { status: 'fulfilled', value: value }; if (count === length) { return resolve(result); } }, (reason) => { result[count++] = { status: 'rejected', reason: reason }; if (count === length) { return resolve(result); } }); } } }); } // 使用 Promise.finally 實現 Promise.allSettled = function(promises) { // 也可使用擴展運算符將 Iterator 轉換成數組 // const promiseArr = [...promises] const promiseArr = Array.from(promises) return new Promise(resolve => { const result = [] const len = promiseArr.length; let count = len; if (len === 0) { return resolve(result); } for (let i = 0; i < len; i++) { promiseArr[i].then((value) => { result[i] = { status: 'fulfilled', value: value }; }, (reason) => { result[i] = { status: 'rejected', reason: reason }; }).finally(() => { if (!--count) { resolve(result); } }); } }); } // 使用 Promise.all 實現 Promise.allSettled = function(promises) { // 也可使用擴展運算符將 Iterator 轉換成數組 // const promiseArr = [...promises] const promiseArr = Array.from(promises) return Promise.all(promiseArr.map(p => Promise.resolve(p).then(res => { return { status: 'fulfilled', value: res } }, error => { return { status: 'rejected', reason: error } }))); };
先來簡單回顧下 Generator 的使用:
function* webCanteenGenerator() { yield '店小二兒,給我切兩斤牛肉來'; yield '再來十八碗酒'; return '好酒!這酒有力氣!'; } var canteen = webCanteenGenerator(); canteen.next(); canteen.next(); canteen.next(); canteen.next(); // {value: "店小二兒,給我切兩斤牛肉來", done: false} // {value: "再來十八碗酒", done: false} // {value: "好酒!這酒有力氣!", done: true} // {value: undefined, done: true}
// 簡易版 // 定義生成器函數,入參是任意集合 function webCanteenGenerator(list) { var index = 0; var len = list.length; return { // 定義 next 方法 // 記錄每次遍歷位置,實現閉包,藉助自由變量作迭代過程當中的「遊標」 next: function() { var done = index >= len; // 若是索引尚未超出集合長度,done 爲 false var value = !done ? list[index++] : undefined; // 若是 done 爲 false,則能夠繼續取值 // 返回遍歷是否完畢的狀態和當前值 return { done: done, value: value } } } } var canteen = webCanteenGenerator(['道路千萬條', '安全第一條', '行車不規範']); canteen.next(); canteen.next(); canteen.next(); // {done: false, value: "道路千萬條"} // {done: false, value: "安全第一條"} // {done: false, value: "行車不規範"} // {done: true, value: undefined}
Generator 缺陷:
async 函數對 Generator 函數改進以下:
async/await 作的事情就是將 Generator 函數轉換成 Promise,說白了,async 函數就是 Generator 函數的語法糖,await 命令就是內部 then 命令的語法糖。
const fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000, data + 1)) const fetchResult = async function () { var result1 = await fetchData(1); var result2 = await fetchData(result1); var result3 = await fetchData(result2); console.log(result3); } fetchResult();
能夠嘗試經過 Babel 官網轉換一下上述代碼,能夠看到其核心就是 _asyncToGenerator
方法。
咱們下面來實現它。
function asyncToGenerator(generatorFn) { // 將 Generator 函數包裝成了一個新的匿名函數,調用這個匿名函數時返回一個 Promise return function() { // 生成迭代器,至關於執行 Generator 函數 // 如上面三碗不過崗例子中的 var canteen = webCanteenGenerator() var gen = generatorFn.apply(this, arguments); return new Promise(function(resolve, reject) { // 利用 Generator 分割代碼片斷,每個 yield 用 Promise 包裹起來 // 遞歸調用 Generator 函數對應的迭代器,當迭代器執行完成時執行當前的 Promise,失敗時則拒絕 Promise function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { // 遞歸終止條件,完成了就 resolve resolve(value); } else { return Promise.resolve(value).then(function(value) { step('next', value); }, function(err) { step('throw', err); }); } } return step('next'); }); } }
好了,本文到這裏就告一段落,若是上述代碼你發現有問題的地方,能夠在評論區留言,一塊兒探討學習。
1.看到這裏了就點個贊支持下吧,你的贊是我創做的動力。
2.關注公衆號前端食堂,你的前端食堂,記得按時吃飯!
3.本文已收錄在前端食堂 github.com/Geekhyt,求個小星星,感謝Star。