Promise是什麼
Promise 是非同步程式設計的一種解決方案。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise物件。
常見傳統非同步解決方案
1、回撥方法
2、事件
3、釋出-訂閱模式
4、觀察者模式
5、其它...
Promise的優勢
比傳統的解決方案——更合理和更強大。
已逐漸成為現在前端程式設計中的一種良好編碼實踐,使其程式碼結構更加合理、可讀性更強。
分步:實現自己的Promise
第一步:從promise的用法中收集它的特點
/*******************************************************************************
- 分析總結:
- 1、Promise是一個類, 接受一個執行函式(excurtor),此執行函式會立即執行
- 2、excurtor函式接受兩個方法引數, 分別為resolve、reject;呼叫時可以傳入一個值,此值可以是任意值
- 3、呼叫resolve,會執行then方法的成功回撥(第一個引數)
- 4、呼叫reject,會執行then方法的失敗回撥(第二個引數)
- 5、promise有三種狀態:pending(初始狀態)、resolved、rejected;
- 只能從pending變為resolved或rejeted, 狀態一旦確定,不能再次改變
- 6、每個例項都有then方法,可以多次then, then方法的引數個數(2個,1個,0個),當then為空時,會將狀態傳遞下去,給下一次then
- 7、呼叫then時,如果已成功,則執行成功回撥,並把成功的內容傳遞過去;反之,失敗亦然;
- 8、如果類的執行函式執行丟擲異常時,則會走失敗回撥,且傳入異常
- 9、呼叫then時,會返回一個新的promise,實現鏈式呼叫;
- 10、then執行時,如果回撥有返回結果,則會執行下一次的成功回撥,並把值傳入;如果丟擲了異常,則會走下一次的失敗回撥
- 11、失敗了也可以再成功,如沒有返回值,則返回undefined
- 12、catch會捕獲沒有捕獲到的錯誤
- 13、如果回撥中返回的是一個promise, 會等待其執行,來決定走下一次的成功還是失敗回撥
- 14、自己不能等待自己完成,否則會報一個“迴圈引用”的型別錯誤
- 15、為什麼連結呼叫時不能返回this? 因為狀態確定後就不能再改變 ******************************************************************************/
第二步:Promise骨架 (簡潔版)
/* ----------------------------------------------------------------------------
* Promise 骨架板
--------------------------------------------------------------------------- */
const states = {
PENDING : 'pending',
FULFILLED: 'fulfilled',
REJECTED : 'rejected'
}
class Promise {
/**
* [ 建構函式 ]
* @param {[function]} excurtor [執行函式]
* @return {[undefined]} undefined
*/
constructor (excurtor) {
let self = this;
this.status = states.PENDING; // 當前狀態
this.value = undefined; // 成功的內容
this.reason = undefined; // 失敗的原因
this.onFulfilled = []; // 儲存成功回撥
this.onRejected = []; // 儲存失敗回撥
// 成功方法
function resolve (value) {
let onFulfilled = self.onFulfilled;
// 判斷狀態是否為pending,只有此狀態時,才可以發生狀態改變
if (self.status === states.PENDING) {
self.status = states.FULFILLED;
self.value = value;
if (onFulfilled.length) {
onFulfilled.forEach(fn => {
fn();
});
}
}
}
// 失敗方法
function reject (reason) {
let onRejected = self.onRejected;
// 判斷狀態是否為pending,只有此狀態時,才可以發生狀態改變
if (self.status === states.PENDING) {
self.status = states.REJECTED;
self.reason = reason;
if (onRejected.length) {
onRejected.forEach(fn => {
fn()
});
}
}
}
// 立即呼叫執行函式,並捕獲異常
try {
excurtor(resolve, reject);
} catch (e) {
reject(e); // 捕獲到執行函式異常後,直接執行失敗回撥
}
}
/**
* [ 例項方法: then ]
* @param {[function]} onFulfilled [成功回撥]
* @param {[function]} onRejected [失敗回撥]
* @return {[undefined]} undefined
*/
then (onFulfilled, onRejected) {
let self = this;
// 狀態已改變為成功時,則立即執行
if (self.status === states.FULFILLED) {
onFulfilled(self.value);
}
// 狀態已改變為失敗時,則立即執行
if (self.status === states.REJECTED) {
onRejected(self.reason);
}
// 處理非同步情況,先存在起來,等狀態改變再執行相應回撥
if (self.status === states.PENDING) {
self.onFulfilled.push(() => {
onFulfilled(self.value);
});
self.onRejected.push(() => {
onRejected(self.reason);
});
}
}
}
// 測試例子 ---------------------------------------------------------------------
let promise = new Promise((resolve, reject) => {
//setTimeout(() => {
resolve(100);
//}, 1000)
});
promise.then(
value => console.log(value),
reason => console.log(`fail: ${ reason }`)
);
複製程式碼
第三步:Promise完整版
/* ----------------------------------------------------------------------------
* Promise 完整版
--------------------------------------------------------------------------- */
const states = {
PENDING : 'pending',
FULFILLED: 'fulfilled',
REJECTED : 'rejected'
}
class Promise {
/**
* [ 建構函式 ]
* @param {[Function]} excurtor [執行函式]
* @return {[Undefined]} undefined
*/
constructor (excurtor) {
let self = this;
this.status = states.PENDING; // 當前狀態
this.value = undefined; // 成功的內容
this.reason = undefined; // 失敗的原因
this.onFulfilled = []; // 儲存成功回撥
this.onRejected = []; // 儲存失敗回撥
// 成功方法
function resolve (value) {
let onFulfilled = self.onFulfilled;
// 判斷狀態是否為pending,只有此狀態時,才可以發生狀態改變
if (self.status === states.PENDING) {
self.status = states.FULFILLED;
self.value = value;
if (onFulfilled.length) {
onFulfilled.forEach(fn => {
fn();
});
}
}
}
// 失敗方法
function reject (reason) {
let onRejected = self.onRejected;
// 判斷狀態是否為pending,只有此狀態時,才可以發生狀態改變
if (self.status === states.PENDING) {
self.status = states.REJECTED;
self.reason = reason;
if (onRejected.length) {
onRejected.forEach(fn => {
fn()
});
}
}
}
// 立即呼叫執行函式,並捕獲異常
try {
excurtor(resolve, reject);
} catch (e) {
reject(e); // 捕獲到執行函式異常後,直接執行失敗回撥
}
}
/**
* [ 例項方法: then ]
* @param {[Function]} onFulfilled [成功回撥]
* @param {[Function]} onRejected [失敗及異常回撥]
* @return {[Object]} promise2
*/
then (onFulfilled, onRejected) {
// 補全引數,實現往下傳遞
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : error =>{ throw error };
let self = this;
let promise2;
promise2 = new Promise((resolve, reject) => {
// 狀態已改變為成功時,則立即執行
if (self.status === states.FULFILLED) {
setTimeout(() => { // 此定時器確保回撥方法在非同步中執行的,也方便進行promiseA+規範測試
// try catch 用來捕獲回撥方法中丟擲的異常
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
// 狀態已改變為失敗時,則立即執行
if (self.status === states.REJECTED) {
setTimeout(() => { // 同上
// 同上
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
// 處理非同步情況,先存在起來,等狀態改變再執行相應回撥
if (self.status === states.PENDING) {
self.onFulfilled.push(() => {
setTimeout(() => { // 同上
// 同上
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
self.onRejected.push(() => {
setTimeout(() => { // 同上
// 同上
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
/**
* [ 例項方法:catch ]
* @param {[Function]} onRejected [失敗及異常回撥]
* @return {[Object]} promise
*/
catch (onRejected) {
return this.then(null, onRejected);
}
/**
* [ 靜態方法(由類呼叫):all ]
* @param {[Array]} promises [由promise組成的陣列]
* @return {[Object]} promise
*/
static all (promises) {
let res = [];
let count = 0;
let length = promises.length;
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise.then(
value => {
res[index] = value;
if (++count === length) {
resolve(res);
}
},
reject
);
});
});
}
/**
* [ 靜態方法(由類呼叫)race ]
* @param {[Array]} promises [由promise組成的陣列]
* @return {[Object]} promise
*/
static race (promises) {
return new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(resolve, reject);
})
});
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 自己不能等待自己的狀態改變
if (promise2 === x) {
return reject(new TypeError('迴圈引用'));
}
let called; // 標識位,要麼成功,要麼失敗;
let typeX = typeof x;
// 判斷x的型別是否為物件或者函式且不為null
if (x !== null && (typeX === 'object' || typeX === 'function')) {
try {
let then = x.then; // 獲取then方法; 此處的try用於防止第三方庫中獲取值會拋錯
if (typeof then === 'function') { // 說明返回的是promise事例
// 執行then方法,並將this指向x
then.call(x, y => {
if (called) return;
called = true;
// 如果返回的還是promise, 則將會繼續遞迴呼叫
resolvePromise(promise2, y, resolve, reject);
}, e => {
if (called) return;
called = true;
reject(e);
});
} else { // x為變通物件時,直接成功
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
// 為了方便promiseA+規範測試
Promise.defer = Promise.deferred = function () {
let dtd = {};
dtd.promise = new Promise((resolve, reject) => {
dtd.resolve = resolve;
dtd.reject = reject;
});
return dtd;
};
module.exports = Promise;
// 測試例子 ---------------------------------------------------------------------
let promise = new Promise((resolve, reject) => {
resolve(1000);
//reject(100);
});
promise
.then(
value => {
console.log('then:', value);
return new Promise((resolve, reject) => {
//resolve(199);
reject('error123');
});
},
reason => console.log(`fail: ${ reason }`)
)
.then(
value => console.log(value),
reason => console.log(`fail: ${ reason }`)
);
Promise.all([
new Promise((resolve, reject) => {
resolve(1);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 1000);
}),
new Promise((resolve, reject) => {
resolve(3);
//reject('出錯了')
})
]).then(
value => console.log(value),
e => console.log(e)
);
複製程式碼
Promise/A+ 參考資料及測試外掛
2、promises-aplus-tests
簡介:此包模組可以幫助我們測試自已寫的Promise是否符合上面的規範。
命令: promises-aplus-tests promise.js
說明:命令後的檔名(promise.js) 為你自己定義的檔名