使用Promise
能解決回撥地獄,多個非同步請求等問題。那麼它是怎麼實現的呢?
讓我們一起來實現一下吧
同步呼叫的實現
首先,我們要知道:
- Promise是一個類
- new Promise 時,會返回一個promise的物件,它會傳一個執行器(executor),這個執行器是立即執行的
- 另外每個promise例項上都會有一個then方法,引數分別是成功(有成功的值)和失敗(有失敗的原用)兩個方法
- promise有三個狀態:成功態,失敗態,等待態。
- 預設狀態是等待態,等待態可以變成成功態或失敗態
- 一旦成功或失敗就不能再變會其他狀態了 知道這些就可以先簡單實現一下了
class Promise {
constructor(executor) { //executor執行器
this.status = 'pending'; //預設等待狀態
this.value = undefined; //成功的值
this.reason = undefined //失敗的原用
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'resolved'; //成功
this.value = value;
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'; //失敗
this.reason = reason;
}
}
executor(resolve, reject); //預設上執行器執行
}
then(onFufilled, onRejected) {
if (this.status === 'resolved') { //成功態
onFufilled(this.value);
}
if (this.status === 'rejected') { //失敗態
onRejected(this.reason);
}
}
}
module.exports = Promise
複製程式碼
以上,我們就簡單的實現了一個同步的promise。 測試一下吧
let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
複製程式碼
列印結果:
但是,我們知道,promise主要解決的是非同步回撥問題。所以,非同步呼叫必須實現起來。
非同步呼叫的實現
當非同步呼叫時,當呼叫例項的then時,狀態可能還處於pending狀態,這時我們需要在例項上定義兩個存放成功和失敗方法的陣列,把需要執行的方法分別放到對應的陣列裡,等到非同步時間到達的時候,再去執行對應陣列裡的方法。
class Promise {
constructor(executor) { //executor執行器
this.status = 'pending'; //預設等待狀態
this.value = undefined; //成功的值
this.reason = undefined //失敗的原用
+ //存放then成功,失敗的回撥的陣列
this.onResovleCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'resolved'; //成功
this.value = value;
+ this.onResovleCallbacks.forEach(fn => fn());
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'; //失敗
this.reason = reason;
+ this.onRejectedCallbacks.forEach(fn => fn());
}
}
executor(resolve, reject); //預設上執行器執行
}
then(onFufilled, onRejected) {
if (this.status === 'resolved') { //成功態
onFufilled(this.value);
}
if (this.status === 'rejected') { //失敗態
onRejected(this.reason);
}
+ if (this.status === 'pending') {
this.onResovleCallbacks.push(() => {
onFufilled(this.value)
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
module.exports = Promise
複製程式碼
以上,我們就實現了promise的非同步呼叫。 測試一下吧
let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve('hello')
},100)
})
promise.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
複製程式碼
列印結果:
非正常執行處理
當執行的時候丟擲異常時,我們應該讓它當狀態變為rejected,去執行then的錯誤方法。 這時候,需要在執行器執行的時候 捕獲一下錯誤,並作出rejected處理
try {
executor(resolve, reject);
} catch (e) { //捕獲到異常時,直接走失敗
reject(e);
}
複製程式碼
測試一下吧
let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
throw new Error('❌')
})
promise.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
複製程式碼
列印結果:
鏈式呼叫的實現
說到鏈式呼叫,我們們接觸最多的就是jquery,jquery實現鏈式呼叫是靠的是返回this,promise實現鏈式呼叫是不是也返回this呢?答案是,
NO !它實現鏈式呼叫靠的是返回一個新的promise
。 在then方法裡,無論promise處於哪種狀態,執行完後,都返回一個新的promise。
then(onFufilled, onRejected) {
+ let promise2; //返回的新promise
+ promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
onFufilled(this.value);
}
if (this.status === 'rejected') {
onRejected(this.reason);
}
if (this.status === 'pending') {
this.onResovleCallbacks.push(() => {
onFufilled(this.value)
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
});
+ return promise2;
}
複製程式碼
鏈式呼叫之錯誤情況
在then中,無論是成功的回撥還是失敗的回撥,只要返回了結果就會走下一個then中的成功,如果有錯誤,就會走下一個then的失敗回撥。即:下一個then的狀態跟上一個then執行時候的狀態無關。 所以,在then執行的時候,
onFufilled, onRejected
可能會出錯,這時候,我們需要捕獲錯誤,並處理成失敗
promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
try {
onFufilled(this.value);
} catch (e) {
reject(e)
}
}
if (this.status === 'rejected') {
try {
onRejected(this.reason);
} catch (e) {
reject(e)
}
}
if (this.status === 'pending') {
this.onResovleCallbacks.push(() => {
try {
onFufilled(this.value)
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
try {
onRejected(this.reason)
} catch (e) {
reject(e);
}
})
}
});
複製程式碼
測試一下吧
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then((data) => {
console.log(data)
throw new Error('?')
}, (err) => {
console.log(err)
}).then((data) => {
console.log(data)
}, (err) => {
console.log('?' + err)
})
複製程式碼
列印結果:
鏈式呼叫之相容多種情況
- 如果第一個promise返回一個普通值,直接將這個返回值,傳遞給下一次then的resolve。
- 如果第一個promise返回一個promise,需要等待返回的這個promise執行後的結果,傳給下一次then 處理第一次promise執行後的返回值x,then方法的每個狀態都需要處理一下:
try {
//x是上一個promise返回值,可能是一個普通值,也可能是一個promise;x也可能是別人的promise,我們可以寫一個方法,統一處理
let x=onFufilled(this.value);
//入參:下一次then的例項promise2,這次返回值x,promise2的成功方法,promise2的失敗方法
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e)
}
複製程式碼
下面來實現resolvePromise
,用來處理多套promise共用的情況:
/*
* resolvePromise
* @Parameters
* promise2: 下一次then的例項promise2
* x: 這次返回值x
* resolve: promise2的成功方法
* reject: promise2的失敗方法
*/
function resolvePromise(promise2, x, resolve, reject) {
//x可能是別人的promise,所以儘可能的允許別人瞎寫
if (promise2 === x) { //返回的結果和promise是同一個,那麼永遠不會成功
return reject(new TypeError('迴圈引用'));
}
let called;//是否呼叫過成功或失敗
// 看x是不是promise。promise應該是一個物件
if (x != null && (typeof x === 'object' || typeof x === 'function')) { //可能是promise
try {
let then = x.then; // 如果是物件 就試著取一下then方法 如果有then,認為它是promise
if (typeof then === 'function') { // 如果then是函式,是promise
then.call(x, y => {
// 成功和失敗只能呼叫一個
if (called) return;
called = true;
// resolve的結果依舊是promise 那就繼續解析
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true;
reject(r); // 失敗了就失敗了
})
} else {
resolve(x); // 直接成功即可
}
} catch (e) { // 取then出錯了那就不要在繼續執行了
if (called) return;
called = true;
reject(e);
}
} else { //普通值 讓promise2直接變成成功態
resolve(x);
}
};
複製程式碼
測試一下吧
- 返回一個普通值
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then((data) => {
console.log(data)
throw new Error('?')
}, (err) => {
console.log(err)
}).then((data) => {
console.log(data)
}, (err) => {
console.log('?' + err)
})
複製程式碼
列印結果:
- 返回一個promise
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then((data) => {
console.log(data)
return new Promise((resolve, reject) => {
resolve('?')
})
}, (err) => {
console.log(err)
}).then((data) => {
console.log(data)
}, (err) => {
console.log('?' + err)
})
複製程式碼
列印結果:
以上,我們的promise好像已經差不多了,但是還有一個問題,需要處理。原始碼可以在hen中實現什麼都不傳。promise中管這種現象叫,值的穿透
。
因此,我們需要在then方法裡,對then方法的入參進行容錯處理:
onFufilled = typeof onFufilled === 'function' ? onFufilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err};
複製程式碼
測試一下吧
let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then().then((data) => {
console.log(data)
}, (err) => {
console.log('?' + err)
})
複製程式碼
列印結果:
另外,promise規範中要求,所有的onFufilled
和onRejected
都需要非同步執行,如果不加非同步可能造成測試的不穩定性,所以我們給執行這兩個方法執行的地方都加上非同步方法。
if (this.status === 'resolved') {
setTimeout(() => {
try {
let x=onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e)
}
}, 0);
}
複製程式碼
Promise實現
class Promise {
constructor(executor) { //executor執行器
this.status = 'pending'; //預設等待狀態
this.value = undefined; //成功的值
this.reason = undefined //失敗的原用
this.onResovleCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'resolved'; //成功
this.value = value;
this.onResovleCallbacks.forEach(fn => fn());
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'; //失敗
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject); //預設上執行器執行
} catch (e) { //捕獲到異常時,直接走失敗
reject(e);
}
}
then(onFufilled, onRejected) {
onFufilled = typeof onFufilled === 'function' ? onFufilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
};
function resolvePromise(promise2, x, resolve, reject) {
//x可能是別人的promise,所以儘可能的允許別人瞎寫
if (promise2 === x) { //返回的結果和promise是同一個,那麼永遠不會成功
return reject(new TypeError('迴圈引用'));
}
//
let called;
// 看x是不是promise。promise應該是一個物件
if (x != null && (typeof x === 'object' || typeof x === 'function')) { //可能是promise
try {
let then = x.then; // 如果是物件 我就試著取一下then方法 如果有then,認為它是promise
if (typeof then === 'function') { // then是函式,是promise
then.call(x, y => {
// 成功和失敗只能呼叫一個
if (called) return;
called = true;
// resolve的結果依舊是promise 那就繼續解析
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true;
reject(r); // 失敗了就失敗了
})
} else {
resolve(x); // 直接成功即可
}
} catch (e) { // 取then出錯了那就不要在繼續執行了
if (called) return;
called = true;
reject(e);
}
} else { //普通值 讓promise2直接變成成功態
resolve(x);
}
};
let promise2; //返回的新promise
promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
setTimeout(() => {
try {
let x = onFufilled(this.value); //x是上一個promise返回值,可能是一個普通值,也可能是一個promise;x也可能是別人的promise,我們可以寫一個方法,統一處理
resolvePromise(promise2, x, resolve, reject); //下一次then的例項promise2,這次返回值x,promise2的成功方法,promise2的失敗方法
} catch (e) {
reject(e)
}
}, 0);
}
if (this.status === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === 'pending') {
this.onResovleCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFufilled(this.value)
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
})
}
});
return promise2;
}
}
module.exports = Promise
複製程式碼
測試
以上,我們基本完成了一個自己的promise庫。
最後,看看這個庫可不可行,那麼就需要測試。官方給出了一個測試的庫promises-aplus-tests
,它會幫我們校驗,這個庫是否可行。另外測試需要用defer
,它是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
複製程式碼
執行
promises-aplus-tests ./myPromise.js
複製程式碼
以上,我們就自己完成了一個基於Promise A+規範的Promise。