上一節, 咱們介紹了js中promise的靜態方法和實例方法, 以及使用. 本節將手寫一個符合promiseA+規範的promise。javascript
新建一個xpromise.js的文件, 建一個class,取名爲XPromise. 把靜態方法和實例方法的簽名加上.java
代碼以下:git
class XPromise {
constructor(cb) {
}
then(resolve, reject) {
}
catch(reject) {
}
finally(cb) {
}
static resolve(val) {
}
static reject(reason) {
}
static all(promises) {
}
static race(promises) {
}
static allSettled(promises) {
}
static deferred() {
}
}
複製代碼
接下來, 咱們實現每個方法.github
描述: 咱們先思考一下, Promise構造器裏應該要作什麼.npm
new Promise((resolve, reject) => {...})
複製代碼
咱們就來實現以上功能, 代碼以下:數組
// 定義promise的狀態, 方便後面使用.
const s = {
pending: 'pending',
fulfilled: 'fulfilled',
rejected: 'rejected'
};
constructor(cb) {
// 設置初始狀態爲pending.
this.status = s.pending;
// 存儲成功後的值.
this.value = null;
// 存儲失敗的緣由.
this.reason = null;
// 執行成功的回調蒐集.
this.resolveCallbacks = [];
// 執行失敗的回調蒐集
this.rejectCallbacks = [];
const resolve = val => {
// 若是狀態是pending, 就更改.
if (this.status === s.pending) {
this.status = s.fulfilled;
this.value = val;
this.resolveCallbacks.forEach(cb => cb())
}
};
const reject = reason => {
if (this.status === s.pending) {
this.status = s.rejected;
this.reason = reason;
this.rejectCallbacks.forEach(cb => cb());
}
};
cb(resolve, reject);
}
複製代碼
描述: 先思考一下, then方法, 要作哪些事情promise
// then的使用
xx.then(result => {}, error => {})
xx.then('test', 'error occurrs')
複製代碼
代碼以下:bash
then(resolve, reject) {
// 對參數作檢查.
// 可能的調用: .then('success', 'error')
// 若是不是一個方法, 就使用默認的函數.進行值傳遞便可.
const onResolved = typeof resolve === 'function' ? resolve : val => val;
const onRejected = typeof reject === 'function' ? reject : err => {throw err};
// 返回的是另外一個promise對象.
const newPromise = new XPromise((resolve, reject) => {
// 若是調用then的時候promise的狀態已經變爲完成.
// 那麼調用成功的回調, 並傳遞參數.
if (this.status === s.fulfilled) {
// 使用setTimeout來模擬異步.
setTimeout(() => {
// 若是執行回調時, 發生異常.
// 那麼就將異常做爲promise失敗的緣由.
try {
let result = onResolved(this.value);
// 調用resolvePromise函數, 更具result的值來決定newPromise的狀態.
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.status === s.rejected) {
// 使用setTimeout來模擬異步.
setTimeout(() => {
// 若是執行回調時, 發生異常.
// 那麼就將異常做爲promise失敗的緣由.
try {
let result = onRejected(this.reason);
// 調用resolvePromise函數, 更具result的值來決定newPromise的狀態.
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
// 若是調用then時狀態仍是pending. 說明promise執行
// 內部的resolve或reject是異步的. 須要把then中的成功回調和失敗回調
// 先存儲起來, 等等promise的狀態改成成功或失敗的時候再執行.
if (this.status === s.pending) {
this.resolveCallbacks.push(() => {
setTimeout(() => {
try {
const result = onResolved(this.value);
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0)
});
this.rejectCallbacks.push(() => {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return newPromise;
}
/** * 處理then中的返回結果. 方便鏈式調用. * @param {*} promise * @param {*} result * @param {*} resolve * @param {*} reject * @returns {XPromise} 返回一個promise. */
const resolvePromise = (promise, result, resolve, reject) => {
// 參數判斷.
// 判斷result和promise是否爲相同的引用.防止循環引用.
if (promise === result) {
return reject(new TypeError('循環引用了.'));
}
// 用來記錄promise的狀態是否改變過.
// 一旦改變就不能再次更改爲其餘的狀態.
let isCalled;
if(result !== null &&
(typeof result === 'object' || typeof result === 'function')){
try {
const then = result.then;
if(typeof then === 'function'){
then.call(result, newResult => {
if(isCalled)return;
isCalled = true;
// 這個newResult可能依舊是一個promise對象, 因此要遞歸調用.
resolvePromise(promise, newResult, resolve, reject);
}, error => {
if(isCalled)return;
isCalled = true;
reject(error);
});
}else{
resolve(result);
}
} catch (error) {
if(isCalled)return;
isCalled = true;
reject(error);
}
}else{
// 若是是一個普通值 那麼就直接把result做爲promise
// 的成功的回調.
resolve(result);
}
};
複製代碼
描述: catch方法相對簡單,其實就是執行then函數的第二個參數.異步
catch(reject) {
// 實際執行的是then的第二個參數.
return this.then(null, reject);
}
複製代碼
描述: 其實就是then在只是成功和失敗的回調時都要執行.函數
finally(cb) {
// then的resolve和reject都要執行.
return this.then(cb, cb);
}
複製代碼
描述: 返回一個promise, 執行成功的回調. 代碼實現以下, 有沒有以爲很是簡單.
static resolve(val) {
return new XPromise((resolve) => resolve(val));
}
複製代碼
描述: 返回一個promise, 執行失敗的回調
static reject(reason) {
return new XPromise((resolve, reject) => reject(reason));
}
複製代碼
描述: 返回一個promise:
static all(promises) {
return new XPromise((resolve, reject) => {
const arr = [];
promises.forEach((p, i) => {
p.then(data => {
arr[i] = data;
}, reject);
});
resolve(arr);
});
}
複製代碼
描述: 返回一個promise,一旦參數中某個promise執行成功或失敗, 就當即執行對於的回調.
static race(promises) {
return new XPromise((resolve, reject) => {
promises.forEach((p) => {
// resolve後,狀態不能改變
p.then(resolve, reject);
});
});
}
複製代碼
描述: 返回一個promise對象, 給定的全部promise都被成功解析或被失敗解析,而且每一個對象都描述每一個promise的結果.
static allSettled(promises) {
return new XPromise((resolve, reject) => {
const arr = [];
promises.forEach((p, i) => {
p.then(data => {
arr[i] = data;
}, reason => {
arr[i] = reason;
});
});
// 等待全部都執行完成後, 再返回.
resolve(arr);
});
}
複製代碼
描述: 返回一個普通對象, 裏面包含一個promise.
static deferred() {
const defer = {}
defer.promise = new XPromise((resolve, reject) => {
defer.resolve = resolve
defer.reject = reject
})
return defer
}
複製代碼
const s = {
pending: 'pending',
fulfilled: 'fulfilled',
rejected: 'rejected'
};
/** * 出來then中的返回結果. 方便鏈式調用. * @param {*} promise * @param {*} result * @param {*} resolve * @param {*} reject * @returns {XPromise} 返回一個promise. */
const resolvePromise = (promise, result, resolve, reject) => {
// 參數判斷.
// 判斷result和promise是否爲相同的引用.防止循環引用.
if (promise === result) {
return reject(new TypeError('循環引用了.'));
}
// 用來記錄promise的狀態是否改變過.
// 一旦改變就不能再次更改爲其餘的狀態.
let isCalled;
if(result !== null &&
(typeof result === 'object' || typeof result === 'function')){
try {
const then = result.then;
if(typeof then === 'function'){
then.call(result, newResult => {
if(isCalled)return;
isCalled = true;
// 這個newResult可能依舊是一個promise對象, 因此要遞歸調用.
resolvePromise(promise, newResult, resolve, reject);
}, error => {
if(isCalled)return;
isCalled = true;
reject(error);
});
}else{
resolve(result);
}
} catch (error) {
if(isCalled)return;
isCalled = true;
reject(error);
}
}else{
// 若是是一個普通值 那麼就直接把result做爲promise
// 的成功的回調.
resolve(result);
}
};
class XPromise {
static resolve(val) {
return new XPromise((resolve) => resolve(val));
}
static reject(reason) {
return new XPromise((resolve, reject) => reject(reason));
}
/** * - 若是參數中全部都執行成功,那麼此實例執行成功的回調 * - 若是參數中有一個執行失敗, 那麼此實例當即執行失敗的回調. 失敗的緣由是第一個promise失敗的結果. * @param {Array} promises Promise集合. * @returns {XPromise} 返回一個promise. */
static all(promises) {
return new XPromise((resolve, reject) => {
const arr = [];
promises.forEach((p, i) => {
p.then(data => {
arr[i] = data;
}, reject);
});
resolve(arr);
});
}
static race(promises) {
return new XPromise((resolve, reject) => {
promises.forEach((p) => {
// resolve後,狀態不能改變
p.then(resolve, reject);
});
});
}
static allSettled(promises) {
return new XPromise((resolve, reject) => {
const arr = [];
promises.forEach((p, i) => {
p.then(data => {
arr[i] = data;
}, reason => {
arr[i] = reason;
});
});
// 等待全部都執行完成後, 再返回.
resolve(arr);
});
}
static deferred() {
const defer = {}
defer.promise = new XPromise((resolve, reject) => {
defer.resolve = resolve
defer.reject = reject
})
return defer
}
constructor(cb) {
// 設置初始狀態爲pending.
this.status = s.pending;
// 存儲成功後的值.
this.value = null;
// 存儲失敗的緣由.
this.reason = null;
// 執行成功的回調蒐集.
this.resolveCallbacks = [];
// 執行失敗的回調蒐集
this.rejectCallbacks = [];
const resolve = val => {
// 若是狀態是pending, 就更改.
if (this.status === s.pending) {
this.status = s.fulfilled;
this.value = val;
this.resolveCallbacks.forEach(cb => cb())
}
};
const reject = reason => {
if (this.status === s.pending) {
this.status = s.rejected;
this.reason = reason;
this.rejectCallbacks.forEach(cb => cb());
}
};
cb(resolve, reject);
}
then(resolve, reject) {
// 對參數作檢查.
// 可能的調用: .then('success', 'error')
// 若是不是一個方法, 就使用默認的函數.進行值傳遞便可.
const onResolved = typeof resolve === 'function' ? resolve : val => val;
const onRejected = typeof reject === 'function' ? reject : err => {throw err};
// 返回的是另外一個promise對象.
const newPromise = new XPromise((resolve, reject) => {
// 若是調用then的時候promise的狀態已經變爲完成.
// 那麼調用成功的回調, 並傳遞參數.
if (this.status === s.fulfilled) {
// 使用setTimeout來模擬異步.
setTimeout(() => {
// 若是執行回調時, 發生異常.
// 那麼就將異常做爲promise失敗的緣由.
try {
let result = onResolved(this.value);
// 調用resolvePromise函數, 更具result的值來決定newPromise的狀態.
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.status === s.rejected) {
// 使用setTimeout來模擬異步.
setTimeout(() => {
// 若是執行回調時, 發生異常.
// 那麼就將異常做爲promise失敗的緣由.
try {
let result = onRejected(this.reason);
// 調用resolvePromise函數, 更具result的值來決定newPromise的狀態.
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
// 若是調用then時狀態仍是pending. 說明promise執行
// 內部的resolve或reject是異步的. 須要把then中的成功回調和失敗回調
// 先存儲起來, 等等promise的狀態改成成功或失敗的時候再執行.
if (this.status === s.pending) {
this.resolveCallbacks.push(() => {
setTimeout(() => {
try {
const result = onResolved(this.value);
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0)
});
this.rejectCallbacks.push(() => {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return newPromise;
}
catch(reject) {
// 實際執行的是then的第二個參數.
return this.then(null, reject);
}
finally(cb) {
// then的resolve和reject都要執行.
return this.then(cb, cb);
}
}
module.exports = XPromise;
複製代碼
npm install -D promises-aplus-tests
複製代碼
npx promises-aplus-tests ./xpromise.js
複製代碼
測試結果以下:872個cases, 所有經過測試.