Promise 是非同步程式設計的一種解決方案,比傳統的解決方案——回撥函式和事件——更合理和更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise物件。 @阮老師
先來看看promise
的幾種用法:
- Promise.prototype.then:then方法有兩個引數,第一個引數是Promise成功時執行的callback,第二個是Promise失敗時執行的callback,then方法返回的是一個新的Promise例項
new Promise((resolve, reject) => resolve()).then(() => {
console.log('success');
}, () => {
console.log('err')
})
複製程式碼
- Promise.prototype.catch:用於指定發生錯誤時的回撥函式
如果沒有指定reject函式,最後就會執行catch函式
new Promise((resolve, reject) => {
reject('err');
})
.then()
.catch(e => {
console.log(e); // => Error: err
})
複製程式碼
- Promise.resolve:定義在Promise類本身的方法,可以通過Promise.resolve()呼叫,相當於直接將狀態改為成功態
Promise.resolve('hahaha').then(data => {
console.log(data); // => 'hahaha'
})
複製程式碼
- Promise.reject:定義在Promise類本身的方法,可以通過Promise.reject()呼叫,相當於直接將狀態改為失敗
Promise.reject('hahaha').then(data => {
console.log(data); // => 'hahaha'
})
複製程式碼
- Promise.prototype.all:將多個Promise執行的結果放到一個陣列中返回
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise1');
}, 1500);
})
let promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise2');
}, 2000);
})
Promise.all([promise1, promise2]).then(data => {
console.log(data); // => ["promise1", "promise2"]
})
複製程式碼
- ...
下面我們自己來實現一個符合 PromiseA+規範 的Promise
我們先來簡單的用一下promise
:
console.log(1)
new Promise(() => {
console.log(2)
});
console.log(3)
複製程式碼
因為我們都知道promise
是非同步的,所以按理會依次輸出 1,3,2
然後我們執行之後發現 它依次輸出的是 1,2,3
當我們使用then
的時候:
console.log(1)
Promise.resolve().then(() => {
console.log(2);
})
console.log(3)
複製程式碼
再次執行,發現結果和我們之前想的是一樣的,會依次輸出 1,3,2
這是因為promise
的callback
是立即執行的,只有then
方法是非同步的
A promise must be in one of three states: pending, fulfilled, or rejected.
- promise必須有三個狀態 pending(等待態) fulfilled(成功態) rejected(失敗態)
- 當 state 是
pending
的時候- 可以將當前狀態改變成其他狀態(fulfilled or rejected)
- 當 state 是
fulfilled
的時候- 不能轉換成其他狀態
- 必須有一個
value
(成功之後的值)
- 當 state 是
rejected
的時候- 不能轉換成其他狀態
- 必須有一個
reason
(失敗的原因)
- 當 state 是
class Promise {
constructor(executor) {
// 每一個promise例項都有自己的三個狀態,我們用一個變數來儲存當前的狀態
this.status = 'pending';
// 用來儲存執行成功時的值
this.value;
// 用來儲存執行失敗時的原因
this.reason;
// 用來將當前狀態改變為成功態的方法
let resolve = val => {
// 只有當前狀態是`pending` 才可以改變狀態和值
if (this.status === 'pending') {
this.value = val;
this.status = 'fulfilled';
}
}
// 用來將當前狀態改變為失敗態的方法
let reject = reason => {
// 只有當前狀態是`pending` 才可以改變狀態和值
if (this.status === 'pending') {
this.reason = reason;
this.status = 'rejected';
}
}
// 執行executor可能會直接丟擲異常,我們用try catch包起來
try {
executor(resolve, reject);
} catch (e) {
// 如果丟擲異常,我們直接將狀態改為失敗態
reject(e);
}
}
}
複製程式碼
then
方法
一個promise必須提供一個
then
方法去獲取當前的或最終的value or reason
promise的
then
方法有兩個引數,分別是成功時執行的方法onFulfilled
和失敗時執行的方法onRejected
onFulfilled
和onRejected
都是可選的引數- 如果沒傳,我們需要設定一個預設方法
- 如果
onFulfilled
是一個function
- 它必須在當前
promise
的狀態變成fulfilled
之後被呼叫,將promise的value
(成功之後的值)作為他的第一個引數 - 在狀態是
fulfilled
之前不能呼叫它 - 只能被呼叫一次
- 它必須在當前
- 如果
onRejected
是一個function
- 它必須在當前
promise
的狀態變成rejected
之後被呼叫,將promise的reason
(失敗的原因)作為他的第一個引數 - 在狀態是
rejected
之前不能呼叫它 - 只能被呼叫一次
- 它必須在當前
promise.then(onFulfilled, onRejected)
複製程式碼
class Promise {
constructor(executor) {...}
then(onFulfilled, onRejected) {
// 如果當前狀態是成功態,我們就執行成功的回撥,並將存起來的成功的值value傳過去
if (this.status === 'fulfilled') {
onFulfilled(this.value);
}
// 如果當前狀態是失敗態,我們就執行失敗的回撥,並將存起來的失敗原因reason傳過去
if (this.status === 'rejected') {
onRejected(this.reason);
}
}
}
複製程式碼
好了,我們現在已經可以簡單的測試一下了
new Promise((resolve, reject) => {
resolve('完美');
}).then(data => {
console.log(data); // => 完美
})
複製程式碼
完美,但是那麼問題來了,如果我們的 resolve
或 reject
是非同步的呢?
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('完美');
}, 1000);
}).then(data => {
console.log(data);
}, err => {
console.log(err);
})
複製程式碼
執行之後我們發現什麼都沒有輸出,onFulfilled
和 onRejected
都沒有列印東西,那說明他們都沒有執行,let me think think...
這是因為如果我們的 resolve
或 reject
是非同步的,當我們的then
執行的時候,狀態還沒有改變,還是pending
狀態,所以當然什麼都不會執行。我們可以先把 onFulfilled
和 onRejected
存起來,等狀態改變的時候依次執行對應的callback。A: 這難道是?B: 沒錯,這就是訂閱釋出模式。下面我們來改寫一下:
class Promise {
constructor(executor) {
this.status = 'pending';
this.value;
this.reason;
// 用來儲存成功時執行的回撥函式
this.onSuccessCallback = [];
// 用來儲存失敗時執行的回撥函式
this.onErrorCallback = [];
let resolve = val => {
if (this.status === 'pending') {
this.value = val;
this.status = 'fulfilled';
// 狀態改變時 依次執行成功的回撥
this.onSuccessCallback.forEach(fn => fn());
}
}
let reject = reason => {
if (this.status === 'pending') {
this.reason = reason;
this.status = 'rejected';
// 狀態改變時 依次執行失敗的回撥
this.onErrorCallback.forEach(fn => fn());
}
}
// 執行executor可能會直接丟擲異常,我們用try catch包起來
try {
executor(resolve, reject);
} catch (e) {
// 如果丟擲異常,我們直接將狀態改為失敗態
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.status === 'fulfilled') {
onFulfilled(this.value);
}
if (this.status === 'rejected') {
onRejected(this.reason);
}
if (this.status === 'pending') {
// 將成功回撥和失敗回撥都存起來,等待狀態改變,再依次執行對應的方法
this.onSuccessCallback.push(() => {
onFulfilled(this.value);
});
this.onErrorCallback.push(() => {
onRejected(this.reason);
});
}
}
}
複製程式碼
-
onFulfilled
和onRejected
都是非同步呼叫(微任務) -
onFulfilled
和onRejected
必須作為一個函式被執行 -
then
方法必須返回一個promise
promise2 = promise1.then(onFulfilled, onRejected) 複製程式碼
- 如果
onFulFilled
oronRejected
返回了一個值x
,執行promise解析程式 - 如果
onFulfilled
oronRejected
丟擲了一個異常e
,promise2
的狀態必須是rejected
,並將e
作為onRejected
的引數 - 如果
onFulfilled
不是一個function
並且promise1
是fulfilled
,promise2
必須也是fulfilled
並且使用和promise1
相同的value
- 如果
onRejected
不是一個function
並且promise1
是rejected
,promise2
必須也是rejected
並且使用和promise1
相同的reason
我們用promise的時候經常
promise.then().then().then() ...
這種寫法叫鏈式呼叫,那怎麼樣才能繼續呼叫then
方法呢,規範規定then
方法必須返回一個promise例項,這樣就可以實現鏈式呼叫了class Promise { contructor() {...} then(onFulfilled, onRejected) { // 如果onFulfilled和onFulfilled 不是一個函式,我們給一個預設值 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; // 我們將所有東西都包到一個promise例項中,最後返回這個例項,這樣就可以實現鏈式呼叫 let promise2; // `onFulfilled` 和 `onRejected`都是非同步呼叫,我們先用一個定時器實現非同步呼叫 promise2 = new Promise((resolve, reject) => { if (this.status === 'fulfilled') { setTimeout(() => { try { // 有一個返回值x,執行解析函式resolvePromise let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }, 0); } if (this.status === 'rejected') { setTimeout(() => { try { // 有一個返回值x,執行解析函式resolvePromise let x = onRejected(this.reason);; resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }, 0); } if (this.status === 'pending') { // 將成功回撥和失敗回撥都存起來,等待狀態改變,再依次執行對應的方法 this.onSuccessCallback.push(() => { setTimeout(() => { try { // 有一個返回值x,執行解析函式resolvePromise let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }, 0); }); this.onErrorCallback.push(() => { setTimeout(() => { try { // 有一個返回值x,執行解析函式resolvePromise let x = onRejected(this.reason);; resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }, 0); }); } }); return promise2; } } 複製程式碼
這裡可能就會有疑惑了,如果有一個返回值
x
就執行promise解析程式resolvePromise
,這是什麼鬼?我們先來看看規範:
promise的解析程式是將
promise2
和x
作為引數的函式 - 如果
-
如果
promise2
和x
是一個promise,那麼丟擲一個TypeError
-
如果
x
是一個object
或function
- 使用變數
then
儲存x.then
- 如果
x.then
會丟擲異常e
,呼叫promise的reject
,並將e
作為它的引數 - 如果
then
是一個function
,使用call
把它的this
指向x
,它的第一個引數是resolvePromise
,第二個引數是rejectPromise
:- 當
resolvePromise
被y
值呼叫的時候,繼續執行解析程式 - 當
rejectPromise
執行的時候,呼叫promise的reject
並將將失敗原因r
作為它的引數
- 當
- 如果
then
不是一個object
orfunction
,呼叫promise的resolve
並將x
作為它的引數
- 使用變數
-
如果
x
不是一個object
orfunction
,呼叫promise的resolve
並將x
作為它的引數
總結下來就兩點:
-
如果
promise2
和x
相等,就丟擲一個TypeError
,我們先來看一下let p = new Promise((resolve, reject) => { // 返回當前promise例項 return p; }); p.then(data => { console.log(data); }, err => { console.log(err); }); 複製程式碼
執行上面程式碼,我們會發現promise丟擲了一個異常,他告訴我們
TypeError: Chaining cycle detected for promise
,這是因為p
的成功還是失敗取決於自己,自己再等待自己的執行結果,所以他既不會成功也不會失敗- 如果
onFulFilled
oronRejected
返回了一個值x
,執行promise解析程式resolvePromise
返回值
x
有可能是一個常量,物件,也有可能是一個promise,這個程式的作用就是如果x
是一個promise,那就將x
一直解析到常量位置let p = new Promise((resolve, reject) => { resolve(new Promise((resolve, reject) => { resolve(1111); })) }) 複製程式碼
let resolvePromise = (promise2, x, resolve, reject) => { // 如果promise2和x相等,就丟擲一個型別錯誤 if (promise2 === x) { return reject(new TypeError('錯了')); } // 只允許呼叫一次resolvePromise let called; // 如果x不是一個常量繼續解析 if (x !== null && (typeof x === 'function' || typeof x === 'object')) { // 呼叫x.then的時候可能會報錯,因為我們有可能和別人的Promise庫混用 try { let then = x.then; // 如果then是一個函式,證明x是一個promise,繼續解析 if (typeof then === 'function') { then.call(x, y => { if (called) { return; } else { called = true; } resolvePromise(promise2, y, resolve, reject); }, r => { if (called) { return; } else { called = true; } reject(r); }) } else { // 說明x可能是一個普通物件,不是一個promise resolve(x); } } catch (e) { if (called) { return; } else { called = true; } reject(e); } } else { // 說明x是一個常量,直接執行resolve resolve(x); } } 複製程式碼
接下來我們來實現
Promise.resolve
Promise.reject
Promise.all
class Promise { contructor {...} then() {...} // 其實Promise.reject和Promise.reject非常簡單 static resolve(value) { return new Promise((resolve) => { resolve(value); }) } static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) } // all方法 static all(promises) { return new Promise((resolve, reject) => { // 用來儲存結果 let arr = []; let index = 0; const saveData = (i, data) => { arr[i] = data; if (++index === promises.length) { resolve(arr); } } for (let i = 0; i < promises.length; i++) { promises[i].then(data => { saveData(i, data); }, reject) } }) } } 複製程式碼
好了,接下來我們來一個完整版的promise
class Promise { contructor() { this.status = 'pending'; this.value; this.reason; // 用來儲存成功時執行的回撥函式 this.onSuccessCallback = []; // 用來儲存失敗時執行的回撥函式 this.onErrorCallback = []; let resolve = val => { if (this.status === 'pending') { this.value = val; this.status = 'fulfilled'; // 狀態改變時 依次執行成功的回撥 this.onSuccessCallback.forEach(fn => fn()); } } let reject = reason => { if (this.status === 'pending') { this.reason = reason; this.status = 'rejected'; // 狀態改變時 依次執行失敗的回撥 this.onErrorCallback.forEach(fn => fn()); } } // 執行executor可能會直接丟擲異常,我們用try catch包起來 try { executor(resolve, reject); } catch (e) { // 如果丟擲異常,我們直接將狀態改為失敗態 reject(e); } } static resolve(value) { return new Promise((resolve) => { resolve(value); }) } static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) } static all(promises) { return new Promise((resolve, reject) => { // 用來儲存結果 let arr = []; let index = 0; const saveData = (i, data) => { arr[i] = data; if (++index === promises.length) { resolve(arr); } } for (let i = 0; i < promises.length; i++) { promises[i].then(data => { saveData(i, data); }, reject) } }) } then(onFulfilled, onRejected) { // 如果onFulfilled和onFulfilled 不是一個函式,我們給一個預設值 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; // 我們將所有東西都包到一個promise例項中,最後返回這個例項,這樣就可以實現鏈式呼叫 let promise2; // `onFulfilled` 和 `onRejected`都是非同步呼叫,我們先用一個定時器實現非同步呼叫 promise2 = new Promise((resolve, reject) => { if (this.status === 'fulfilled') { setTimeout(() => { try { // 有一個返回值x,執行解析函式resolvePromise let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }, 0); } if (this.status === 'rejected') { setTimeout(() => { try { // 有一個返回值x,執行解析函式resolvePromise let x = onRejected(this.reason);; resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }, 0); } if (this.status === 'pending') { // 將成功回撥和失敗回撥都存起來,等待狀態改變,再依次執行對應的方法 this.onSuccessCallback.push(() => { setTimeout(() => { try { // 有一個返回值x,執行解析函式resolvePromise let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }, 0); }); this.onErrorCallback.push(() => { setTimeout(() => { try { // 有一個返回值x,執行解析函式resolvePromise let x = onRejected(this.reason);; resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }, 0); }); } }); return promise2; } } let resolvePromise = (promise2, x, resolve, reject) => { // 如果promise2和x相等,就丟擲一個型別錯誤 if (promise2 === x) { return reject(new TypeError('錯了')); } // 只允許呼叫一次resolvePromise let called; // 如果x不是一個常量繼續解析 if (x !== null && (typeof x === 'function' || typeof x === 'object')) { // 呼叫x.then的時候可能會報錯,因為我們有可能和別人的Promise庫混用 try { let then = x.then; // 如果then是一個函式,證明x是一個promise,繼續解析 if (typeof then === 'function') { then.call(x, y => { if (called) { return; } else { called = true; } resolvePromise(promise2, y, resolve, reject); }, r => { if (called) { return; } else { called = true; } reject(r); }) } else { // 說明x可能是一個普通物件,不是一個promise resolve(x); } } catch (e) { if (called) { return; } else { called = true; } reject(e); } } else { // 說明x是一個常量,直接執行resolve resolve(x); } } 複製程式碼
- 如果