GitHub
前端
若是你已經掌握了Promise的基本用法,咱們進行下一步es6
說到Promise/A+規範,不少同窗可能很不理解這是一個什麼東西,下面給出兩個地址,不瞭解的同窗須要先了解一下,對咱們後續理解源碼頗有幫助,先看兩遍,有些地方看不懂也不要緊,後續咱們能夠經過源碼來回頭再理解,想把一個知識真的學會,就要反覆琢磨,從【確定->否認->再確定】不斷地深刻理解,直到徹底掌握。github
Promise/A+規範英文地址
Promise/A+規範中文翻譯面試
若是你看過了Promise/A+規範,咱們繼續,我會帶着你們按照規範要求,一步一步的來實現源碼算法
一個promise必須處於三種狀態之一: 請求態(pending), 完成態(fulfilled),拒絕態(rejected)npm
咱們先找需求來完成這一部分代碼,一個簡單的小架子數組
// 2.1 狀態常量
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
// Promise構造函數
function MyPromise(fn) {
const that = this;
this.state = PENDING;
this.value = null;
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
function resolve() {
if (that.state === PENDING) {
}
}
function reject() {
if (that.state === PENDING) {
}
}
}
複製代碼
上面這段代碼完成了Promise構造函數的初步搭建,包含:promise
下面咱們來完成resolve和rejectbash
function resolve(value) {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}
function reject(value) {
if (that.state === PENDING) {
that.state = REJECTED
that.value = value
that.rejectedCallbacks.map(cb => cb(that.value))
}
}
複製代碼
記下來咱們須要來執行新建Promise傳入的函數體
try {
fn(resolve, reject);
} catch (e){
reject(e)
}
複製代碼
在執行過程當中可能會遇到錯誤,須要捕獲錯誤傳給reject
promise必須提供then方法來存取它當前或最終的值或者緣由。
promise的then方法接收兩個參數:
promise.then(onFulfilled, onRejected)
複製代碼
現根據這些要求咱們先實現個簡單的then函數:
MyPromise.prototype.then = function (onFulfilled, onRejected) {
const that = this
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected =
typeof onRejected === 'function'
? onRejected
: r => {
throw r
}
if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}
if (that.state === RESOLVED) {
onFulfilled(that.value)
}
if (that.state === REJECTED) {
onRejected(that.value)
}
}
複製代碼
如上咱們就完成了一個簡易版的promise,可是還不能徹底知足Promise/A+規範,接下來咱們繼續完善
— 2.2.6.1 若是/當 promise 完成執行(fulfilled),各個相應的onFulfilled回調 必須根據最原始的then 順序來調用 — 2.2.6.2 若是/當 promise 被拒絕(rejected),各個相應的onRejected回調 必須根據最原始的then 順序來調用
promise2 = promise1.then(onFulfilled, onRejected);
複製代碼
接下來根據規範需求繼續完善then函數裏的代碼:
if (that.status === 'PENDING') {
promise2 = new Promise(function (resolve, reject) {
that.resolvedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onFulfilled(that.value);
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
});
that.rejectedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onRejected(that.value);
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
});
})
}
if (that.status === 'RESOLVED') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () { //用setTimeOut實現異步
try {
let x = onFulfilled(that.value); //x多是普通值 也多是一個promise, 還多是別人的promise
resolutionProcedure(promise2, x, resolve, reject) //寫一個方法統一處理
} catch (e) {
reject(e);
}
})
})
}
if (that.status === 'REJECTED') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(that.value);
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
})
}
複製代碼
若是一個promise被一個thenable resolve,而且這個thenable參與了循環的thenable環, [[Resolve]](promise, thenable)的遞歸特性最終會引發[[Resolve]](promise, thenable)再次被調用。 遵循上述算法會致使無限遞歸,鼓勵(但不是必須)實現檢測這種遞歸併用包含信息的TypeError做爲reason拒絕(reject)
這部分規範主要描述了resolutionProcedure函數的規範,下面咱們來實現resolutionProcedure這個函數,我先我麼你關注2.3.4下面那段話,簡單的來講規定了x不能與promise2相等,這樣會發生循環引用的問題,以下栗子:
let p = new MyPromise((resolve, reject) => {
resolve(1)
})
let p1 = p.then(value => {
return p1
})
複製代碼
因此咱們須要先進行檢測,代碼以下:
function resolutionProcedure(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Error'))
}
}
複製代碼
接下來咱們判斷x的類型
if (x instanceof MyPromise) {
x.then(function (value) {
resolutionProcedure(promise2, value, resolve, reject)
}, reject)
}
複製代碼
若是 x 爲 Promise 的話,須要判斷如下幾個狀況:
最後咱們來完成剩餘的代碼:
let called = false
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return
called = true
resolutionProcedure(promise2, y, resolve, reject)
},
e => {
if (called) return
called = true
reject(e)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
複製代碼
有專門的測試腳本能夠測試所編寫的代碼是否符合PromiseA+的規範 首先,在promise實現的代碼中,增長如下代碼:
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
複製代碼
安裝測試腳本:
npm install -g promises-aplus-tests
複製代碼
若是當前的promise源碼的文件名爲promise.js
那麼在對應的目錄執行如下命令:
promises-aplus-tests promise.js
複製代碼
共有872條測試用例,能夠完美經過
這樣咱們就完成了符合Promise/A+規範的源碼,下面是整個代碼:
const PENDING = 'pending';
const RESOLVED = 'resolve';
const REJECTED = 'rejected';
function Promise(fn) {
let that = this;
that.status = 'PENDING';
that.value = undefined;
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
if (that.status === 'PENDING') {
that.status = 'RESOLVED';
that.value = value;
that.resolvedCallbacks.map(cb => cb(that.value));
}
}
function reject(value) {
if (that.status === 'PENDING') {
that.status = 'REJECTED';
that.value = value;
that.rejectedCallbacks.map(cb => cb(that.value));
}
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
function resolutionProcedure(promise2, x, resolve, reject) {
//有可能這裏返回的x是別人的promise 要儘量容許其餘人亂寫
if (promise2 === x) {//這裏應該報一個循環引用的類型錯誤
return reject(new TypeError('循環引用'));
}
//看x是否是一個promise promise應該是一個對象
let called; //表示是否調用過成功或者失敗
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
//多是promise 看這個對象中是否有then 若是有 姑且做爲promise 用try catch防止報錯
try {
let then = x.then;
if (typeof then === 'function') {
//成功
then.call(x, function (y) {
if (called) return //避免別人寫的promise中既走resolve又走reject的狀況
called = true;
resolutionProcedure(promise2, y, resolve, reject)
}, function (err) {
if (called) return
called = true;
reject(err);
})
} else {
resolve(x) //若是then不是函數 則把x做爲返回值.
}
} catch (e) {
if (called) return
called = true;
reject(e)
}
} else { //普通值
return resolve(x)
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
//成功和失敗默認不傳給一個函數
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
return value;
}
onRejected = typeof onRejected === 'function' ? onRejected : function (err) {
throw err;
}
let that = this;
let promise2; //新增: 返回的promise
if (that.status === 'PENDING') {
promise2 = new Promise(function (resolve, reject) {
that.resolvedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onFulfilled(that.value);
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
});
that.rejectedCallbacks.push(function () {
setTimeout(function () {
try {
let x = onRejected(that.value);
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
});
})
}
if (that.status === 'RESOLVED') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () { //用setTimeOut實現異步
try {
let x = onFulfilled(that.value); //x多是普通值 也多是一個promise, 還多是別人的promise
resolutionProcedure(promise2, x, resolve, reject) //寫一個方法統一處理
} catch (e) {
reject(e);
}
})
})
}
if (that.status === 'REJECTED') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(that.value);
resolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
})
}
return promise2;
}
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = Promise;
複製代碼
以上就是符合Promise/A+規範的源碼,ES6的Promise其實並非向咱們這樣經過js來實現,而是在底層實現,而且還擴展了不少新的方法:
這裏就不一一介紹啦,你們能夠參考阮一峯老師的文章 ES6入門-Promise對象
這篇文章給你們講解的Promise/A+規範的源碼,但願你們能多讀多寫,深入的體會一下源碼的思想,對之後的開發也頗有幫助。
感謝你們的閱讀,以爲還不錯,辛苦點一下關注,謝謝!