手擼 Promise
- Promise作爲ES6的核心內容,是前端童鞋必備的基礎知識!更是面試通關的必刷題!
- Promise的出現,解決了"蠻荒時代"的回調地獄,讓js異步callback走向簡潔,優雅!
- 本文參照Promise/A+實現(不足之處,請留言指出)
第一版(初出茅廬)
狀態
- Promise有三種狀態值標識當前的異步狀態!
- pending狀態可變爲fulfilled 或 rejected
- 一旦處於fulfilled 或 rejected時 則不可改爲其他狀態
const PENDING = 'pending' // 待定,等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失敗
promise的基本實現
- promise構造函數、resolve方法、reject方法、then方法
// promise構造函數
function Promise(executor) {
this.state = 'pending'
this.value = null // 當狀態fulfilled,給定一個value值
this.reason = null // 當狀態rejected,給定一個失敗原因
// resolve方法
function resolve(value) {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
}
}
// reject方法
function reject(reason) {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
- then方法
當狀態爲fulfilled時調用onFulfilled, 當狀態爲rejected時調用onRejected
Promise.prototype.then = function(onFulfilled, onRejected) {
if(this.state === FULFILLED){
onFulfilled(this.value)
}
if(this.state === FULFILLED){
onFulfilled(this.reason)
}
}
此時基本有了promise的樣子,但Promise真正要解決的異步問題,並未處理!
第二版 克敵(異步)制勝
- 頭腦(漿糊)風暴,思考then方法是同步調用,那必然會在異步任務完成前執行,該如何處理呢?
此時次刻,聰明的你必然想到了訂閱發佈,先通過then訂閱異步成功後要執行的任務,在resolve 或 reject調用後執行已訂閱的任務
// 給Promise中添加兩個事件池,存儲訂閱事件
function Promise(executor) {
......
this.onFulfilledCallBack = [] // onFulfilled訂閱池
this.onRejectedCallBack = [] // onRejected訂閱池
......
// resolve方法、reject方法
function resolve(value) {
if (self.status === PENDING) {
self.status = FULFILLED
self.onFulfilledCallBack.forEach(cb => cb(self.reason))
}
}
function reject(reason) {
if (self.status === PENDING) {
self.status = REJECTED
self.reason = reason
self.onRejectedCallBack.forEach(cb => cb(self.reason))
}
}
// 將onFulfilled, onRejected 放入事件池中
Promise.prototype.then = function(onFulfilled, onRejected) {
if(this.state === FULFILLED){
onFulfilled(this.value)
}
if(this.state === FULFILLED){
onFulfilled(this.reason)
}
if(this.state === PENDING){
this.onFulfilledCallBack.push(onFulfilled)
this.onRejectedCallBack.push(onRejected)
}
}
此時此刻,恭喜你正式成爲一名江湖(江湖)劍客,可以行俠仗義了
帶三版 千秋萬代一統江湖-then方法的鏈式調用
- 根據Promise/A+規範,then方法返回一個新的promise
Promise.prototype.then = function(onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
if (this.state === FULFILLED) {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === REJECTED) {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === PENDING) {
this.onFulfilledCallBack.push(()=>{
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.onRejectedCallBack.push(()=>{
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
})
}
})
return promise2
}
- 注意 resolvePromise 方法,我在調用事件池裏的方法後,爲什麼要resolvePromise方法將新的promise2與回調方法的返回值通過resolvePromise處理呢???
- 思考:若事件池內的方法(then中用戶傳入的回調方法),返回的也是一個promise我們處理邏輯又將如何執行
- 根據示例:觀察原生Promise
let test = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1)
resolve(1)
}, 1000)
})
test
.then(data => {
console.log('data:', data)
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2)
resolve(2)
}, 2000)
}).then(data => {
console.log('data:', data)
console.log(3)
return 3
})
})
.then(data => {
console.log('data:', data)
console.log(4)
})
// 輸出結果
1
data: 1
2
data: 2
3
data: 3
4
- 據此我們可以發現外層的then回調會在內層的promise的then回調全部執行完纔會執行,那內外層的promise必然有着不可告人的祕密,他們就是通過resolvePromise方法“勾結”在一起
resolvePromise方法
- 參數有promise2(then默認返回的promise)、x(then回調中return的對象)、resolve、reject
// 這裏先給出完整的 resolvePromise 代碼,看完後我們來思考幾個問題
function resolvePromise(promise2, x, resolve, reject){
// 處理循環引用
if(x === promise2){
// reject報錯
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止多次調用
let called;
if (x instanceof Promise) {
if (x.status === PENDING) {
x.then(
y => {
resolvePromise(promise2, y, resolve, reject)
},
reason => {
reject(reason)
}
)
} else {
x.then(resolve, reject)
}
} else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// A+規定,聲明then = x的then方法
let then = x.then;
// 如果then是函數,就默認是promise了
if (typeof then === 'function') {
// 就讓then執行 第一個參數是this 後面是成功的回調 和 失敗的回調
then.call(x, y => {
// 成功和失敗只能調用一個
if (called) return;
called = true;
// resolve的結果依舊是promise 那就繼續解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
// 成功和失敗只能調用一個
if (called) return;
called = true;
reject(err);// 失敗了就失敗了
})
} else {
// 返回非promise,內層promise完成,執行promise2的resolve 迴歸到外城then的調用
resolve(x); // 直接成功即可
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
- 思考1: x === promise2,看下例子
let p1 = new Promise(resolve => {
resolve(1);
});
var p2 = p1.then(data => {
return p2;
})
如果不對x === promise2處理,會導致循環引用,自己等待自己
- 思考2: else if (x != null && (typeof x === 'object' || typeof x === 'function'))
兼容符合 Promise/A+ 的 then 方法, 使實現方式不同(只要符合規範)的promise能夠相互調用。
第五版 內外兼修 處理異常
- then中onFulfilled, onRejected均爲非必填參數
- onFulfilled, onRejected均爲異步調用
- onFulfilled, onRejected執行時用try catch捕獲異常然後reject(err)
Promise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected 若未傳,則通過throw err的方式將err拋出
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
};
if (this.state === REJECTED) {
setTimeout(() => {
// 如果報錯
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === PENDING) {
this.onFulfilledCallBack.push(()=>{
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
this.onRejectedCallBack.push(()=>{
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
})
}
})
return promise2
}
- 到此你已完成了一個promise
完整代碼
// state 3種狀態
const PENDING = 'pending' // 待定
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失敗
function Promise(executor) {
this.state = PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledCallBack = []
this.onRejectedCallBack = []
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallBack.forEach(fn => fn())
}
}
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.onRejectedCallBack.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
// onRejected 若未傳,則通過throw err的方式將err拋出
onRejected =
typeof onRejected === 'function'
? onRejected
: err => {
throw err
}
let promise2 = new Promise((resolve, reject) => {
if (this.state === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.state === REJECTED) {
setTimeout(() => {
// 如果報錯
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.state === PENDING) {
this.onFulfilledCallBack.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCallBack.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2
}
function resolvePromise(promise2, x, resolve, reject) {
// 處理循環引用
if (x === promise2) {
// reject報錯
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 防止多次調用
let called
if (x instanceof Promise) {
if (x.status === PENDING) {
x.then(
y => {
resolvePromise(promise2, y, resolve, reject)
},
reason => {
reject(reason)
}
)
} else {
x.then(resolve, reject)
}
} else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// A+規定,聲明then = x的then方法
let then = x.then
// 如果then是函數,就默認是promise了
if (typeof then === 'function') {
// 就讓then執行 第一個參數是this 後面是成功的回調 和 失敗的回調
then.call(
x,
y => {
// 成功和失敗只能調用一個
if (called) return
called = true
// resolve的結果依舊是promise 那就繼續解析
resolvePromise(promise2, y, resolve, reject)
},
err => {
// 成功和失敗只能調用一個
if (called) return
called = true
reject(err) // 失敗了就失敗了
}
)
} else {
// 返回非promise,內層promise完成,執行promise2的resolve 迴歸到外城then的調用
resolve(x) // 直接成功即可
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}