--文末附視頻教程+源碼javascript
new Promise
時,須要傳遞一個 executor 執行器,執行器馬上執行(同步執行),executor 接受兩個參數,分別是 resolve(成功) 和 reject(失敗)。java
promise 有 3 個狀態:git
- pending:初始狀態,既不是成功,也不是失敗狀態。
- fulfilled:成功狀態,意味着操做成功完成。
- rejected:失敗狀態,意味着操做失敗。
複製代碼
const PENDING = Symbol('PENDING')
const RESOLVED = Symbol('RESOLVED')
const REJECTED = Symbol('REJECTED')
// Promise 構造函數
function Promise (executor) {
// 當前的狀態,默認是 pending
this.status = PENDING
// 保存回調函數,由於 then 能夠調用屢次,因此以數組保存
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
// 成功值
this.value = undefined
// 拒絕的緣由
this.reason = undefined
// resolve、reject 是用來改變狀態,
// 而且根據 then 方法註冊回調函數的順序依次調用回調函數
// resolve 是執行成功後調用的函數
const resolve = (value) => {
// 若是狀態不是 pending,說明狀態已經改變,不能再發生變化
if (this.status === PENDING) {
this.value = value
this.status = RESOLVED
this.onResolvedCallbacks.forEach(fn => fn())
}
}
// reject 是執行失敗後調用的函數
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onRejectedCallbacks.forEach(fn => fn())
}
}
// 使用 try...catch... 捕捉代碼執行過程當中可能拋出的異常
try {
// 執行器默認會當即執行
executor(resolve, reject)
} catch(e) {
// 若是執行時發生錯誤(包括手動拋出的異常),等同於執行失敗
reject(e)
}
}
複製代碼
實現 Promise 的 then
方法,then
方法有兩個可選參數,onFulfilled
和 onRejected
,而且必須返回一個 promise 對象。 若是 onFulfilled
或 onRejected
返回的是一個 promise,會自動執行這個 promise,並採用它的狀態。若是成功則將成功的結果向外層的下一個 then 傳遞。es6
Promise.prototype.then = function(onFulfilled, onRejected) {
// onFulfilled 和 onRejected 是可選的,這裏須要對不傳的時候作兼容處理
// onFulfilled 若是不是函數,就構建一個函數,函數直接返回結果。
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected 若是不是函數,就構建一個函數,函數直接拋出異常。
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason;
}
let promise2 = new Promise((resolve, reject) => {
// 狀態爲 resolved 或 rejected 時,主要是 new Promise 時執行器裏面調用 resolve/reject 是同步的
if (this.status === RESOLVED) {
// 使用 setTimeout (宏任務),確保 onFulfilled 和 onRejected 方法異步執行,也確保 promise2 已經定義,
// 若是不使用 setTimeout,會致使執行 resolvePromise(promise2, x, resolve, reject) 時 promise2 未定義而報錯。
setTimeout(() => {
// try...catch... 捕捉代碼錯誤或手動拋出的異常,報錯或異常看成執行失敗處理。異步代碼的報錯沒法被外層的 try...catch... 捕獲
try {
const x = onFulfilled(this.value)
// x 多是 promise 也多是普通值,x 本次 then 調用中 onFulfilled 或 onRejected 回調函數返回的結果,須要傳遞給下一個 then 的回調函數
// 使用公共方法 resolvePromise 處理不一樣狀況,並實現 x 值的傳遞。
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
return
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
// 狀態爲 pending 時,主要是 new Promise 時執行器裏面調用 resolve/reject 是異步的
if (this.status === PENDING) {
// 由於是異步的,不知道什麼時候執行完成,因此這裏先存好回調函數的調用(訂閱),等狀態改變後再執行(發佈)
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
複製代碼
有部分同窗可能會認爲 then
是在 promise 狀態改變後(即有返回值後)才執行,其實 then
是當即執行,是 onFulfilled
和 onRejected
纔在狀態改變後執行。github
Promise 解決程序(promise resolution procedure) 是一個抽象的操做,須要輸入一個 promise 和一個值,咱們表示爲[[Resolve]](promise, x)
。shell
這裏咱們定義公用方法 resolvePromise
來實現這個過程。npm
resolvePromise
主要實現的功能是:json
判斷 promise2
和 x
是否指向同一對象,若是是 promise2
執行失敗而且使用 TypeError 做爲執行失敗的緣由。數組
例如:promise
const p = new Promise((resolve, reject) => resolve(1))
let promise2 = p.then(() => {
// x
return promise2
})
複製代碼
判斷 x
是否是一個 promise 對象,若是是就經過調用 resolve/reject 獲取狀態並向下個 then
傳遞 。
若是 x
是一個普通對象/值,則直接將 x
做爲結果值向下個 then
傳遞。
下面是代碼實現:
const resolvePromise = (promise2, x, resolve, reject) => {
// 若是 promise2 和 x 指向同一對象, promise2 執行失敗而且使用 TypeError 做爲執行失敗的緣由
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<promise>'))
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 防止屢次調用成功或者失敗
let called;
try {
// 首先存儲一個指向 x.then 的引用,而後測試並調用該引用,以免屢次訪問 x.then 屬性
// 預防取 x.then 的時候錯誤,例如: .then 是經過 Object.defineProperty 定義的,定義的 get() {}(getter) 可能代碼錯誤或拋出異常
let then = x.then
// 沒用 x.then 判斷由於怕再次取 .then 的時候出錯。例如:經過 Object.defineProperty 定義的 then 可能第一次調用不報錯,第二次調用報錯或屢次調用返回的值可能不一樣
if (typeof then === 'function') {
// 若是 then 是一個函數,則認爲 x 是一個 promise,以 x 爲 它的 this 調用它, then 調用完成就會取到 x 的狀態,採用 x 的狀態返回
// 而且傳遞兩個回調函數做爲參數,第一個參數是 resolvePromise,第二個參數是 rejectPromise
then.call(x, y => {
if (called) {
return
}
called = true
// y 是 x 調用 then 後成功的結果,採用這個結果
// y 可能仍是一個 promise,因此進行遞歸調用,直到結果是一個普通值
resolvePromise(promise2, y, resolve, reject)
}, r => {
// r 是調用 x.then 後報錯或異常,再也不判斷是不是 promise,直接傳遞
if (called) {
return
}
called = true
reject(r); // 失敗結果向下傳遞
});
} else {
// 普通對象,直接傳遞給下一個 then
resolve(x)
}
} catch (e) {
// 發生代碼錯誤或手動拋出異常,則當執行失敗處理並以 e 爲失敗緣由
if (called) {
return
}
called = true
reject(e)
}
} else {
// 普通值,直接傳遞給下一個 then
resolve(x)
}
}
複製代碼
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
複製代碼
deferred
的做用:
使用 promise-aplus-test
工具須要用到這個方法
這個方法能夠減小代碼嵌套
例如:
const Promise = require('./pormise')
const fs = require('fs')
const readfile = url => {
return new Promise((resolve, reject) => { // 一層嵌套
fs.readFile(url, 'utf-8', (err, data) => { // 二層嵌套
if(err) reject(err)
resolve(data)
})
})
}
readfile('./package.json').then(data => console.log(data))
複製代碼
使用 deferred
:
const readfile = url => {
let dfd = Promise.defer()
// 減小了一層嵌套
fs.readFile(url, 'utf-8', (err, data) => {
if(err) dfd.reject(err)
dfd.resolve(data)
})
return dfd.promise
}
複製代碼
測試使用工具 promises-aplus-test
安裝:npm install -g promises-aplus-test
測試:promise-aplus-test promise.js
使用本文提供的 github源碼 則能夠直接運行如下命令:
// 安裝依賴工具
npm install
// 運行測試指令
npm run test
複製代碼
上面已經實現了 Promise 的核心部分代碼,但原生的 Promise 還提供一些其餘的方法。
有時須要將現有對象轉爲 Promise 對象,Promise.resolve()
方法就起到這個做用。
Promise.resolve()
等價於下面的寫法。
Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))
複製代碼
Promise.resolve
方法的參數分紅四種狀況。
Promise.resolve
不作任何修改,原封不動返回thenable
對象,Promise.resolve
方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable
對象的then
方法。then
方法的對象,或根本就不是對象,Promise.resolve
方法返回一個新的 Promise 對象,狀態爲resolved
resolved
狀態的 Promise 對象。Promise.resolve = function (param) {
if (param instanceof Promise) {
return param;
}
return new Promise((resolve, reject) => {
if (param && param.then && typeof param.then === 'function') {
setTimeout(() => {
param.then(resolve, reject);
});
} else {
resolve(param);
}
});
}
複製代碼
Promise.reject(reason)
方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected
。
Promise.reject()
方法的參數,會原封不動地做爲reject
的理由,變成後續方法的參數。這一點與Promise.resolve
方法不一致。
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
複製代碼
Promise.all()
方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.all([p1, p2, p3]);
複製代碼
上面代碼中,Promise.all()
方法接受一個數組做爲參數,p1
、p2
、p3
都是 Promise 實例,若是不是,就會先調用下面講到的Promise.resolve
方法,將參數轉爲 Promise 實例,再進一步處理。另外,Promise.all()
方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。
p
的狀態由p1
、p2
、p3
決定,分紅兩種狀況。
(1)只有p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態纔會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。
(2)只要p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數。
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
// 存放結果,.all 傳入的參數是數組,返回結果也是數據
let result = []
// 使用計數器,記錄多個異步併發問題
let index = 0
if (promises.length === 0) {
resolve(result)
} else {
// 處理返回值
function processValue(i, data) {
result[i] = data
// 計數器記錄的個數等於傳入的數組長度,說明所有認爲已完成,能夠返回結果
if (++index === promises.length) {
resolve(result)
}
}
for (let i = 0; i < promises.length; i++) {
let current = promises[i]
// 判斷當前的處理對象是 promise 仍是普通值
if (isPromise(current)) {
// 取當前的處理對象的執行結果,若是有一個執行失敗,則直接 reject
current.then(data => {
processValue(i, data)
}, reject)
} else {
processValue(i, current)
}
}
}
})
}
複製代碼
Promise.race()
方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.race([p1, p2, p3]);
複製代碼
上面代碼中,只要p1
、p2
、p3
之中有一個實例率先改變狀態,p
的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p
的回調函數。
Promise.race()
方法的參數與Promise.all()
方法同樣,若是不是 Promise 實例,就會先調用下面講到的Promise.resolve()
方法,將參數轉爲 Promise 實例,再進一步處理。
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
if (promises.length === 0) {
return;
} else {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then((data) => {
resolve(data);
return;
}, (err) => {
reject(err);
return;
});
}
}
});
}
複製代碼
Promise.prototype.catch
方法是 .then(null, rejection)
或 .then(undefined, rejection)
的別名,用於指定發生錯誤時的回調函數。
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
}
複製代碼
finally
方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。該方法是 ES2018 引入標準的。
Promise.prototype.finally = function (callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}
複製代碼