Promise自定義,看我如何征服你
自定義程式碼
這裡是我自定義的Promise,如果想看原版,可以跳過最後有符合PromiseA+規範的源碼
class 承諾 {
constructor(處理器函數) { //1. 處理器函數是在_new 承諾(處理器函數)_的時候當做參數傳入到構造函數里的,構造函數里會執行這個處理器函數.
let self = this //2.註冊函數執行時可能沒有前綴,如果註冊函數里用this可能就是window,這裡把this賦給變數,形成閉包.
this.狀態 = '等待...' //3.這個屬性有三個值,默認是等待,只有調用註冊成功或註冊失敗才會改變這個屬性的值,**只能改變一次**
this.成功值 = undefined //4.這個成功值是由使用者調用註冊成功函數的時候傳進函數里的,然後我們在註冊成功函數給這個屬性賦值,簡單點說就是形參賦值給屬性
this.失敗原因 = undefined //5.同上
//說明:這裡需要了解'那時()'函數,可以看完'那時()'再來看,這兩在非同步的情況才會用到,使用者在調用'那時()'的時候,承諾仍然是等待狀態(也就是說註冊成功和註冊失敗兩個函數沒有被調用)
this.成功回調函數組 = [] //6.承諾變為成功時要調用的函數,多次調用then傳函數,我們就把'那時()'傳進來的函數push到這個數組裡,
this.失敗回調函數組 = [] //7.同上,承諾變為失敗時要調用的函數
let 註冊成功 = function (成功值) { //7.'註冊成功()'函數當實參傳進了處理器函數,這樣使用者在編寫處理器函數得時候可以根據情況調用.
if (self.狀態 === '等待...') { //8.這個判斷的作用:承諾的狀態只能由等待->成功或等待->失敗,而且只能改變一次.我們默認初始態是等待,如果不是等待說明之前已經調用過'註冊成功或註冊失敗'
self.成功值 = 成功值 //9.將使用者調用'註冊成功(成功值)'函數傳進來的值由對象屬性保存,這個值在調用'那時()'傳進來的函數時會用到
self.狀態 = '成功' //10.調用註冊成功,改變承諾狀態為成功
for (let 函數 of self.成功回調函數組) { //11.調用'那時()'傳進來的函數
函數(成功值)
}
// this.成功回調函數組.forEach(函數=>函數())
}
}
let 註冊失敗 = function (失敗原因) { //12. 同上
if (self.狀態 === '等待...') {
self.失敗原因 = 失敗原因
self.狀態 = '失敗'
for (let 函數 of self.失敗回調函數組) {
函數(失敗原因)
}
// this.成功回調函數組.forEach(函數=>函數())
}
}
try {
處理器函數(註冊成功, 註冊失敗) //13.執行處理器函數,把我們定義好的兩個函數傳進去,這樣使用者就可以用這兩個函數來改變承諾的狀態和值
} catch (錯誤) {
註冊失敗(錯誤) //14.處理器函數是使用者編寫的,有可能報錯,出錯了我們就調用註冊失敗()來改變這個承諾的狀態和值
}
}
那時(成功的回調, 失敗的回調) { //15.對象的'那時()'方法,由使用者傳進來兩個函數參數,規定當前承諾對象為成功時調用第一個,失敗調用第二個
let self = this
成功的回調 = typeof 成功的回調 === 'function' ? 成功的回調 : 成功值 => 成功值
失敗的回調 = typeof 失敗的回調 === 'function' ? 失敗的回調 : 失敗原因 => {
throw 失敗原因
} //16.這兩個參數是可選參數,當他們不是函數時我們給他默認值
let 新的承諾 = new 承諾((註冊成功, 註冊失敗) => { //17.返回一個新的承諾,這樣就可以鏈式調用'那時()'了,這裡要注意新承諾的狀態和值由'那時()'傳進來的函數執行情況決定
//18.判斷承諾的狀態,決定立即執行回調還是將回調函數push到回調函數組裡等使用者調用註冊成功()在註冊成功()里執行
if (self.狀態 === '等待...') { //19.如果是等待,顯然承諾的成功值或失敗值還是undefined,所以我們把他push到回調函數組裡,讓他在註冊成功()或註冊失敗()函數里調用
self.成功回調函數組.push(() => { //20.這個函數要非同步,因為我們會在裡面用到新承諾,然而新承諾現在還沒被賦值,要徹底理解這裡應該需要js執行機制知識,目前我還沒有....
setTimeout(() => {
try {
let 回調的返回值 = 成功的回調(self.成功值) //21.調用使用者傳進來的函數得到返回值,如果使用者沒有寫返回語句默認是返回undefined
善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗) //22.根據返回值決定我們這個新承諾的值和狀態 這裡傳進去的是我們新承諾的註冊成功和註冊失敗函數,我們在裡面調用他們來改變我們新承諾的狀態
} catch (錯誤) {
註冊失敗(錯誤) //23.如果執行使用者的函數出錯就把我們新承諾的狀態和值改變
}
})
})
self.失敗回調函數組.push(() => { //24.同上
setTimeout(() => {
try {
let 回調的返回值 = 失敗的回調(self.失敗原因)
善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗)
} catch (錯誤) {
註冊失敗(錯誤)
}
})
})
}
if (self.狀態 === '成功') { //25.如果承諾的狀態是成功的說明註冊成功()函數已經被調用了,承諾的狀態和值都被使用者改變了,
//我們可以取到對應的值來傳進回調里,讓使用者用.
setTimeout(() => {
try { //邏輯同20-23
let 回調的返回值 = 成功的回調(self.成功值)
善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗)
} catch (錯誤) {
註冊失敗(錯誤)
}
})
}
if (self.狀態 === '失敗') { //同上
setTimeout(() => {
try {
let 回調的返回值 = 失敗的回調(self.失敗原因)
善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗)
} catch (錯誤) {
註冊失敗(錯誤)
}
})
}
})
return 新的承諾
}
}
工具函數,這個函數才是真核心
//26.這個是核心,涉及到遞歸,這裡我寫的和PromiseA+規範的實現不同,他判斷的是thenable,兼容性好,我只是為了理解Promise原理所以就簡化了.
function 善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗) {
// let p2 = p.那時((成功值)=>{
// return p2
// })
if (回調的返回值 instanceof 承諾) { //27.判斷使用者寫的回調函數的返回值是不是一個承諾,如果不是承諾就直接調用我們新承諾的註冊成功()函數改變我們新承諾的狀態和值
//如果返回值是一個承諾我們就得到這個承諾的值,把這個值給我們新承諾的註冊函數
if (新的承諾 === 回調的返回值) {
//28.這裡是為了解決這種使用情況
// let p2 = p.那時((成功值)=>{
// return p2
// })
註冊失敗(new TypeError('循環引用'))
return
}
try {
回調的返回值.那時((成功值) => {
善後處理(新的承諾, 成功值, 註冊成功, 註冊失敗) //29.如果使用者返回的承諾的值還是一個承諾,繼續'那時()'直到不是承諾
//注意:這裡傳進去的註冊成功,註冊失敗是我們新承諾的註冊函數,遞歸進去,當不是承諾時就改變我們新承諾的狀態和值了,然後遞歸一層層返回
//這裡是進遞歸,其他情況就是出遞歸
}, (失敗原因) => {
註冊失敗(失敗原因)
})
} catch (錯誤) {
註冊失敗(錯誤)
}
} else {
註冊成功(回調的返回值)
}
}
自定義完成,我們拉出來遛一遛
//測試一
p = new 承諾((註冊成功, 註冊失敗) => {
setTimeout(()=>{
註冊失敗('abc')
},1000)
})
p.那時((成功的值) => {
console.log(成功的值)
},(失敗原因)=>{
console.log(失敗原因)
})
//輸出abc
//測試二:返回值是承諾
p = new 承諾((註冊成功, 註冊失敗) => {
setTimeout(() => {
註冊成功('我是最外面的')
}, 1000)
})
p.那時((成功值) => {
console.log(成功值)
let p11 = new 承諾((註冊成功, 註冊失敗) => {
let p22 = new 承諾((註冊成功, 註冊失敗) => {
註冊成功('最裡層')
})
註冊成功(p22)
})
return p11
}, 1)
.那時((成功值) => {
console.log(成功值)
}, (失敗原因) => {
console.log(失敗原因)
})
//輸出:
//我是最外層
//我是最裡層
//測試三:失敗穿透
p = new 承諾((註冊成功, 註冊失敗) => {
throw '(╥╯^╰╥)'
setTimeout(() => {
註冊成功('♪(´▽`)')
}, 1000)
})
p.那時((成功的值) => {
console.log(成功的值)
},(失敗原因)=>{
console.log('第一次失敗:'+失敗原因)
throw '(╥╯^╰╥))'
}).那時(
(成功的值) => {
console.log(成功的值)
}
).那時(null,(失敗原因) => {
console.log('失敗穿透'+失敗原因)
})
//輸出
//第一次失敗:(╥╯^╰╥)
//失敗穿透(╥╯^╰╥))
到這裡就結束了,下面是通過PromiseA+測試的源程式碼.
function Promise(executor) {
let self = this;
self.value = undefined; // 成功的值
self.reason = undefined; // 失敗的值
self.status = 'pending'; // 目前promise的狀態pending
self.onResolvedCallbacks = []; // 可能new Promise的時候會存在非同步操作,把成功和失敗的回調保存起來
self.onRejectedCallbacks = [];
function resolve(value) { // 把狀態更改為成功
if (self.status === 'pending') { // 只有在pending的狀態才能轉為成功態
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(fn => fn()); // 把new Promise時非同步操作,存在的成功回調保存起來
}
}
function reject(reason) { // 把狀態更改為失敗
if (self.status === 'pending') { // 只有在pending的狀態才能轉為失敗態
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(fn => fn()); // 把new Promise時非同步操作,存在的失敗回調保存起來
}
}
try {
// 在new Promise的時候,立即執行的函數,稱為執行器
executor(resolve, reject);
} catch (e) { // 如果執行executor拋出錯誤,則會走失敗reject
reject(e);
}
}
// then調用的時候,都是屬於非同步,是一個微任務
// 微任務會比宏任務先執行
// onFulfilled為成功的回調,onRejected為失敗的回調
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
}
let self = this;
let promise2;
// 上面講了,promise和jquery的區別,promise不能單純返回自身,
// 而是每次都是返回一個新的promise,才可以實現鏈式調用,
// 因為同一個promise的pending resolve reject只能更改一次
promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
// 為什麼要加setTimeout?
// 首先是promiseA+規範要求的
// 其次是大家寫的程式碼,有的是同步,有的是非同步
// 所以為了更加統一,就使用為setTimeout變為非同步了,保持一致性
setTimeout(() => {
try { // 上面executor雖然使用try catch捕捉錯誤
// 但是在非同步中,不一定能夠捕捉,所以在這裡
// 用try catch捕捉
let x = onFulfilled(self.value);
// 在then中,返回值可能是一個promise,所以
// 需要resolvePromise對返回值進行判斷
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
}
if (self.status === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
self.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
}
});
return promise2
}
function resolvePromise(promise2, x, resolve, reject) {
// 3.從2中我們可以得出,自己不能等於自己
// 當promise2和x是同一個對象的時候,則走reject
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 4.因為then中的返回值可以為promise,當x為對象或者函數,才有可能返回的是promise
let called
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 8.從第7步,可以看出為什麼會存在拋出異常的可能,所以使用try catch處理
try {
// 6.因為當x為promise的話,是存在then方法的
// 但是我們取一個對象上的屬性,也有可能出現異常,我們可以看一下第7步
let then = x.then
// 9.我們為什麼在這裡用call呢?解決了什麼問題呢?可以看上面的第10步
// x可能還是個promise,那麼就讓這個promise執行
// 但是還是存在一個惡作劇的情況,就是{then:{}}
// 此時需要新增一個判斷then是否函數
if (typeof then === 'function') {
then.call(x, (y) => { // y是返回promise後的成功結果
// 一開始我們在這裡寫的是resolve(y),但是考慮到一點
// 這個y,有可能還是一個promise,
// 也就是說resolve(new Promise(...))
// 所以涉及到遞歸,我們把resolve(y)改成以下
// 12.限制既調resolve,也調reject
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
// 這樣的話,程式碼會一直遞歸,取到最後一層promise
// 11.這裡有一種情況,就是不能既調成功也調失敗,只能挑一次,
// 但是我們前面不是處理過這個情況了嗎?
// 理論上是這樣的,但是我們前面也說了,resolvePromise這個函數
// 是所有promise通用的,也可以是別人寫的promise,如果別人
// 的promise可能既會調resolve也會調reject,那麼就會出問題了,所以我們接下來要
// 做一下限制,這個我們寫在第12步
}, (err) => { // err是返回promise後的失敗結果
if (called) return
called = true
reject(err)
})
} else {
if (called) return;
called = true;
resolve(x) // 如果then不是函數的話,那麼則是普通對象,直接走resolve成功
}
} catch (e) { // 當出現異常則直接走reject失敗
if (called) return
called = true
reject(e)
}
} else { // 5.x為一個常量,則是走resolve成功
resolve(x)
}
}
module.exports = Promise;
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
//執行命令promises-aplus-tests promise.js檢測是否符合promiseA+規範,先安裝包