githubjavascript
博客java
Promise
的聲明當咱們使用Promise
的時候,一般都是new Promise((resolve, reject) => {})
。git
所以咱們能夠看出:github
Promise
是一個類;Promise
類的構造函數的第一個參數是函數,這個函數叫處理器函數(executor function
);resolve
和reject
resolve
函數;reject
函數。所以,咱們能夠初步聲明一下Promise
類。shell
class Promise {
/** * 構造器 * @returns {Promise<object>} * @param executor<function>: executor有兩個參數:resolve和reject */
constructor(executor) {
// resolve 成功
const resolve = () => {};
// reject 失敗
const reject = () => {};
// 執行 executor
executor(resolve,reject);
}
}
複製代碼
Promise
存在着三種狀態:pending
(等待態)、fulfilled
(成功態)和rejected
(失敗態):數組
Promise
的初始狀態是pending
狀態;pending
狀態能夠轉換爲fulfilled
狀態和rejected
狀態;fulfilled
狀態不能夠轉爲其餘狀態,且必須有一個不可改變的值(value);rejected
狀態不能夠轉爲其餘狀態,且必須有一個不可改變的緣由(reason);resolve
函數並傳入參數value,則狀態改變爲fulfilled
,且不能夠改變;reject
函數並傳入參數reason,則狀態改變爲rejected
,且不能夠改變;reject
函數。所以,咱們須要在Promise
類中設置三個變量:state
(狀態變量),value
(成功值的變量)和reason
(失敗緣由的變量),而後在resolve
函數、reject
函數以及執行executor
函數報錯的時候改變state
的值。promise
class Promise {
constructor(executor) {
// 初始化狀態
this.state = 'pending';
// 成功的值
this.value = undefined;
// 失敗的緣由
this.reason = undefined;
/** * resolve 成功函數 * @param value<any>: 成功的值 */
const resolve = (value) => {
// 只能在狀態爲pending的時候執行
if(this.state === 'pending'){
// resolve調用後,state轉化爲fulfilled
this.state = 'fulfilled';
// 存儲value
this.value = value;
}
};
/** * reject 失敗函數 * @param reason<any>: 失敗的緣由 */
const reject = (reason) => {
// 只能在狀態爲pending的時候執行
if(this.state === 'pending'){
// resolve調用後,state轉化爲rejected
this.state = 'rejected';
// 存儲reason
this.reason = reason;
}
};
// 若是executor執行報錯,直接執行reject()
try {
executor(resolve,reject);
}catch (e){
reject(e);
}
}
}
複製代碼
then
方法Promise
有一個then
方法,而該方法中有兩個參數:onFulfilled
和onRejected
:markdown
fulfilled
,只執行onFulfilled
,傳入this.value
;rejected
,只執行onRejected
,傳入this.reason
;所以咱們能夠來實現一下then
方法。異步
class Promise {
constructor(executor) {...}
/** * then 方法 * @param onFulfilled<function>: 狀態爲fulfilled時調用 * @param onRejected<function>: 狀態爲rejected時調用 */
then(onFulfilled, onRejected) {
// 狀態爲fulfilled的時候,執行onFulfilled,並傳入this.value
if(this.state === 'fulfilled'){
/** * onFulfilled 方法 * @param value<function>: 成功的結果 */
onFulfilled(this.value)
}
// 狀態爲rejected的時候,onRejected,並傳入this.reason
if(this.state === 'rejected'){
/** * onRejected 方法 * @param reason<function>: 失敗的緣由 */
onRejected(this.reason)
}
}
}
複製代碼
Promise
實際上一個異步操做:async
resolve()
是在setTimeout
內執行的;then()
函數時,若是狀態是pending
時,咱們須要等待狀態結束後,才繼續執行,所以此時咱們須要將then()
的兩個參數onFulfilled
和onRejected
存起來;Promise
實例能夠調用屢次then()
,所以咱們須要將onFulfilled
和onRejected
各類用數組存起來。所以咱們能夠藉着完善代碼:
class Promise {
/** * 構造器 * @returns {Promise<object>} * @param executor<function>: executor有兩個參數:resolve和reject */
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
// 存儲onFulfilled的數組
this.onResolvedCallbacks = [];
// 存儲onRejected的數組
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
// 一旦resolve執行,調用onResolvedCallbacks數組的函數
this.onResolvedCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
// 一旦reject執行,調用onRejectedCallbacks數組的函數
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
// 狀態爲pending的時候,將onFulfilled、onRejected存入數組
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
複製代碼
咱們經常會像下面代碼同樣使用Promise
:
new Promise()
.then()
.then()
.then()
複製代碼
這種方法叫作鏈式調用,一般是用來解決回調地獄(Callback Hell
)的,就以下的代碼:
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))
}
})
})
}
})
複製代碼
爲了實現鏈式調用,咱們須要知足一下幾點:
then()
返回一個新的Promise
實例;then()
返回了一個值,則這個值就是onFulfilled()
或者onRejected()
的值,咱們須要把這個值傳遞到下一個then()
中。而對於上一個then()
的返回值,咱們須要對齊進行必定的處理,所以封裝一個resolvePromise()
的方法去進行判斷處理;
接下來咱們對then()
方法進行修改:
class Promise {
constructor(executor) { ... }
/** * then 方法 * @returns {Promise<object>} * @param onFulfilled<function>: 狀態爲fulfilled時調用 * @param onRejected<function>: 狀態爲rejected時調用 */
then(onFulfilled, onRejected) {
// 返回一個新的Promise實例
const newPromise = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
const x = onFulfilled(this.value)
// 對返回值進行處理
resolvePromise(newPromise, x, resolve, reject);
}
if (this.state === 'rejected') {
const x = onRejected(this.reason);
// 對返回值進行處理
resolvePromise(x, resolve, reject);
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
const x = onFulfilled(this.value);
// 對返回值進行處理
resolvePromise(newPromise, x, resolve, reject);
})
this.onRejectedCallbacks.push(() => {
const x = onRejected(this.reason);
// 對返回值進行處理
resolvePromise(newPromise, x, resolve, reject);
})
}
});
return newPromise;
}
}
function resolvePromise() {}
複製代碼
resolvePromise
函數對於上一個then()
的返回值,咱們用x
變量存起來,而後須要對它進行一個處理:
x
是否是Promise
實例;
Promise
實例,則取它的結果,做爲新的Promise
實例成功的結果;Promise
成功的結果;而後咱們處理返回值後,須要利用newPromise
的resolve
和reject
方法將結果返回。
這裏咱們還須要注意一個地方,就是x
等於newPromise
的話,這時會形成循環引用,致使死循環。
let p = new Promise(resolve => {
resolve(0);
});
const p2 = p.then(data => {
// 循環引用,本身等待本身完成,致使死循環
return p2;
})
複製代碼
所以,resolvePromise
函數須要4個參數,即newPromise
,x
、resolve
和reject
。
因此咱們來實現一下resolvePromise
函數:
/** * resolvePromise 方法 * @param newPromise<object>: 新的Promise實例 * @param x<any>: 上一個then()的返回值 * @param resolve<function>:Promise實例的resolve方法 * @param reject<function>:Promise實例的reject方法 */
function resolvePromise(newPromise, x, resolve, reject) {
// 循環引用報錯
if(x === newPromise){
// reject報錯
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止屢次調用
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
// x 爲Promise實例
if (typeof then === 'function') {
// 使用call執行then(),call的第一個參數是this,後續即then()的參數,即第二個是成功的回調方法,第三個爲失敗的回調函數
then.call(x, y => {
// 成功和失敗只能調用一個
if(called)return;
called = true;
// resolve 的結果依舊是promise實例,那就繼續解析
resolvePromise(newPromise, y, resolve, reject);
}, err => {
// 成功和失敗只能調用一個
if(called)return;
called = true;
// 失敗了就直接返回reject報錯
reject(err);
})
} else {
// x 爲普通的對象或方法,直接返回
resolve(x);
}
} catch (e) {
if(called)return;
called = true;
reject(e);
}
} else {
// x 爲普通的值,直接返回
resolve(x);
}
}
複製代碼
onFulfilled
和onRejected
關於then()
的兩個參數——onFulfilled
和onRejected
:
onFulfilled
不是一個函數,就將它直接替換成函數value => value
;onRejected
不是一個函數,就將它直接替換成函數err => {throw err}
;class Promise {
constructor(executor) { ... }
then(onFulfilled, onRejected) {
// onFulfilled若是不是函數,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected若是不是函數,就忽略onRejected,直接拋出錯誤
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
...
}
}
複製代碼
其次,onFulfilled
和onRejected
是不能同步被調用的,必須異步調用。所以咱們就用setTimeout
解決一步問題。
class Promise {
constructor(executor) { ... }
then(onFulfilled, onRejected) {
// onFulfilled若是不是函數,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected若是不是函數,就忽略onRejected,直接拋出錯誤
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
};
return new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 異步調用
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
}
if (this.state === 'rejected') {
// 異步調用
setTimeout(() => {
try{
const x = onRejected(this.reason);
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
// 異步調用
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
})
this.onRejectedCallbacks.push(() => {
// 異步調用
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
})
}
});
}
}
複製代碼
Promise
的其餘方法Promise.all()
Promise.all()
方法接收一個promise
的iterable
類型的輸入,包括Array
、Map
、Set
。而後返回一個Promise
實例,該實例回調返回的結果是一個數組,包含輸入全部promise
的回調結果。
但只要任何一個輸入的promise
的reject
回調執行或者輸入不合法的promise
,就會立馬拋出錯誤。
/** * Promise.all 方法 * @returns {Promise<object>} * @param promises<iterable>: 一個promise的iterable類型輸入 */
Promise.all = function (promises) {
let arr = [];
return new Promise((resolve, reject) => {
if (!promises.length) resolve([]);
// 遍歷promises
for(const promise of promises) {
promise.then(res => {
arr.push(res);
if(arr.length === promises.length){
resolve(arr);
}
}, reject)
}
})
}
複製代碼
Promise.allSettled()
Promise.allSettled()
其實跟Promise.all()
很像,一樣是接收一個promise
的iterable
類型的輸入,但返回的是一個給定的promise
已經完成後的promise
,並帶有一個對象數組,每一個對象標識着對應的promise
結果。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => console.log(results));
// > Array [Object { status: "fulfilled", value: 3 }, Object { status: "rejected", reason: "foo" }]
複製代碼
實現:
/** * Promise.allSettled 方法 * @returns {Promise<object>} * @param promises<iterable>: 一個promise的iterable類型輸入 */
Promise.allSettled = function (promises) {
let arr = [];
return new Promise((resolve, reject) => {
try {
const processData = (data) => {
arr.push(data);
if(arr.length === promises.length){
resolve(arr);
}
}
if (!promises.length) resolve([]);
// 遍歷promises
for(const promise of promises) {
promise.then(res => {
processData({state:'fulfilled', value: res})
}, err => {
processData({state:'rejected', reason: err})
})
}
}catch (e){
reject(e)
}
})
}
複製代碼
Promise.any()
Promise.any()
跟Promise.all()
和Promise.allSettled()同樣,一樣是接收一個
promise的
iterable類型的輸入。但只要其中的一個
promise成功,就返回那個已經成功的
promise,但若是沒有一個
promise成功,就返回一個失敗的
promise`。
/** * Promise.any 方法 * @returns {Promise<object>} * @param promises<iterable>: 一個promise的iterable類型輸入 */
Promise.any = function (promises) {
return new Promise((resolve, reject) => {
// 若是傳入的參數是一個空的可迭代對象,則返回一個 已失敗(already rejected) 狀態的 Promise
if (!promises.length) reject();
// 若是傳入的參數不包含任何 promise,則返回一個 異步完成 (asynchronously resolved)的 Promise。
if (typeof promises[Symbol.iterator] !== 'function' ||
promises === null ||
typeof promises === 'string') {
resolve()
}
let i = 0;
// 遍歷promises
for (const promise of promises) {
promise.then(res => {
i++;
resolve(res);
}, err => {
i++;
if (i === promises.length) {
reject(err);
}
})
}
})
}
複製代碼
Promise.race()
Promise.race()
,一樣是接收一個promise
的iterable
類型的輸入。一旦迭代器中的某個promise
完成了,無論是成功仍是失敗,就會返回這個promise
。
/** * Promise.race 方法 * @returns {Promise<object>} * @param promises<iterable>: 一個promise的iterable類型輸入 */
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (const promise of promises) {
promise.then(resolve, reject)
}
})
}
複製代碼
Promise.reject()
和Promise.resolve()
Promise.reject()
方法返回一個帶有拒絕緣由的Promise
對象;Promise.resolve()
方法返回一個以定值解析後的Promise
對象。
/** * Promise.reject 方法 * @returns {Promise<object>} * @param val<any> */
Promise.reject = function (val) {
return new Promise(reject => reject(val))
}
/** * Promise.resolve 方法 * @returns {Promise<object>} * @param val<any> */
Promise.resolve = function (val) {
return new Promise(resolve => resolve(val))
}
複製代碼
catch()
和finally()
catch()
方法是用來處理失敗的狀況,它傳入一個處理函數,而後返回一個promise
實例。實際上它是then()
的語法糖,只接受rejected
態的數據。
finally()
是在promise
結束時,不管結果是fufilled
仍是rejected
,都會執行指定的回調函數。一樣也返回一個promise
實例。
class Promise {
constructor(executor) { ... }
then(onFulfilled, onRejected) { ... }
/** * catch 方法 * @returns {Promise<object>} * @param callback<function>: 處理函數 */
catch(callback) {
return this.then(null, callback);
}
/** * finally 方法 * @returns {Promise<object>} * @param callback<function>: 處理函數 */
finally(callback) {
return this.then(res => {
return Promise.resolve(callback()).then(() => res)
}, err => {
return Promise.reject(callback()).then(() => {
throw err
})
})
}
}
複製代碼
Promise/A+
規範: github.com/promises-ap…
Promise/A+
測試工具: github.com/promises-ap…
安裝promises-aplus-tests
插件。
yarn add promises-aplus-tests
複製代碼
在Promise.js
後面插入下列代碼。
// 測試
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = Promise;
複製代碼
而後輸入命令行進行測試。
promises-aplus-tests Promise.js
複製代碼
結果:
872 passing (18s)
複製代碼