寫前端也有幾年了,最近在整理知識點,看了一遍promiseA+的規範,就想自己寫一遍這個前端非同步流程控制的經典函式。
先用個簡單的例子演示一下promise是怎麼工作的。
let myFirstPromise = new Promise(function(resolve, reject){
//當非同步程式碼setTimeout執行成功時,我們才會呼叫resolve(...), 當非同步程式碼失敗時就會呼叫reject(...)
//如果執成功回撥resolve,則永遠不會再走到reject
//promise成功和失敗狀態是我們自己手動控制的
//在本例中,我們使用setTimeout(...)來模擬非同步程式碼,實際編碼時可能是XHR請求或是HTML5的一些API方法.
setTimeout(function(){
resolve("成功!"); //程式碼正常執行!
}, 250);
});
myFirstPromise.then(function(successMessage){
//successMessage的值是上面呼叫resolve(...)方法傳入的值.
//successMessage引數不一定非要是字串型別,這裡只是舉個例子
console.log("Yay! " + successMessage);
});
複製程式碼
下面按promiseA+的規範自己實現一個promise
先寫一個建構函式 供外部呼叫 executor是帶有 resolve 和 reject 兩個引數的函式 。Promise建構函式執行時立即呼叫executor 函式, resolve 和 reject 兩個函式作為引數傳遞給executor。resolve 和 reject 函式被呼叫時,分別將promise的狀態改為fulfilled(完成)或rejected(失敗)。executor 內部通常會執行一些非同步操作,一旦完成,可以呼叫resolve函式來將promise狀態改成fulfilled,或者在發生錯誤時將它的狀態改為rejected。 如果在executor函式中丟擲一個錯誤,那麼該promise 狀態為rejected。executor函式的返回值被忽略。
var promise = new Promise(function(resolve, reject) {
// 成功的話呼叫resolve並傳入value
// 失敗的話呼叫reject並傳入reason
})
複製程式碼
第二步 寫建構函式的框架
function Promise(executor) {
let self = this
//由於promise非同步回撥方法,所以必須有等待狀態,成功狀態,失敗狀態。
self.status = 'pending' // Promise當前的狀態
self.data = undefined // Promise的值
self.onResolvedCallback = [] // Promise resolve時的回撥函式集,因為在Promise結束之前有可能有多個回撥新增到它上面
self.onRejectedCallback = [] // Promise reject時的回撥函式集,因為在Promise結束之前有可能有多個回撥新增到它上面
executor(resolve, reject) // 執行executor並傳入相應的引數
}
複製程式碼
第三步,實現resolve和reject和內部丟擲異常時立即將promise改為reject狀態
function Promise(executor) {
let self = this
self.status = 'pending' // Promise當前的狀態
self.data = undefined // Promise的值
self.onResolvedCallback = [] // Promise resolve時的回撥函式集,因為在Promise結束之前有可能有多個回撥新增到它上面
self.onRejectedCallback = [] // Promise reject時的回撥函式集,因為在Promise結束之前有可能有多個回撥新增到它上面
function resolve(value) {
//resolve方法,後邊會用到
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
//有可能呼叫多次resolve,把多個回撥存到成功陣列裡
for(var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
}
function reject(reason) {
// reject方法,後邊會用到
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
//原因同上
for(var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason)
}
}
}
try { // 執行executor的過程出錯,所以我們用try/catch塊給包起來,並且在出錯後以catch到的值reject掉這個Promise
executor(resolve, reject) // 執行executor
} catch(e) {
//出錯之後呼叫reject
reject(e)
}
}
複製程式碼
promise的then方法(解決洋蔥式的語法結構)
原生Promise物件有一個then方法,用來實現在這個Promise狀態確定後的回撥那麼這個then方法需要寫在原型鏈上。then方法會返回一個新的Promise,這樣就實現了鏈式呼叫,從而避免了之前callback回撥的那種洋蔥結構,使程式更簡單易懂。
Promise.prototype.then = function(onResolved, onRejected) {
let self = this
let promise2
// 根據promiseA+,如果then的引數不是function,則我們需要忽略它,此處以如下方式處理
onResolved = typeof onResolved === 'function' ? onResolved : function(v) {}
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {}
if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
})
}
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
})
}
if (self.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
})
}
}
複製程式碼
因為then方法返回一個新的promise,所以我們需要在then裡面執行onResolved或者onRejected,並根據返回值來確定新的promise的結果,而且,如果onResolved/onRejected返回的是一個Promise,promise2將直接取這個Promise的結果:直接看程式碼
Promise.prototype.then = function(onResolved, onRejected) {
var self = this
var promise2
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}
if (self.status === 'resolved') {
// 如果promise狀態已經確定並且是resolved,我們呼叫onResolved
// 因為考慮到有可能丟擲異常,所以我們將其包在try/catch塊裡
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) { // 如果onResolved的返回值是一個Promise物件,直接取它的結果做為promise2的結果,做到鏈式呼叫
x.then(resolve, reject)
}
resolve(x) // 否則,以它的返回值做為promise2的結果
} catch (e) {
reject(e) // 如果出錯,以捕獲到的錯誤做為promise2的結果
}
})
}
// 此處邏輯與resolve相同
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
}
if (self.status === 'pending') {
// 由於Promise還有pending狀態,我們並不能確定呼叫onResolved還是onRejected,需要確定promise的狀態才能進行下一步的處理
// 邏輯與resolve一致
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
})
}
}
複製程式碼
這樣我我們就實現了一個簡單的promise,與官方promise基本一致,不過還有個問題,如果呼叫多個then,不過並沒有返回值,例如這樣
new Promise(resolve=>resolve(“呵呵呵”))
.then()
.then()
.then(function foo(value) {
alert(value)
})
複製程式碼
只在最後一個then出現返回值,那麼就需要把promise的返回值直接傳到最後一個then中,這裡,我們做一個值的穿透,如果沒有返回值,那麼就把resolve向下拋。
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}
複製程式碼
最後一點,在看promiseA+規範的時候發現then的回撥需要非同步執行,這裡沒有仔細思考過原因,在網上找過一些資料後覺得,promise非同步執行的原因主要是防止棧溢位和保持執行的一致性,promiseA+的標準給出的是要返回new Promise到jobQuene js的執行時裡對jobQuene和eventLoop的處理還是有一些差異的。不同之處在於 每個 JavaScript Runtime 可以有多個 Job Queue,但只有一個 Event Loop Queue當 JavaScript Engine 處理完當前事件佇列中的程式碼後,再執行本次任務中所有的 Job Queue,然後再處理 Event Loop Queue(下一次事件迴圈任務) 所以這裡為了模擬jobQuene 我們把promise執行回撥放在setTimeout中,保證回撥非同步執行,上程式碼
function Promise(executor) {
var self = this
self.status = 'pending'
self.onResolvedCallback = []
self.onRejectedCallback = []
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
// 非同步回撥函式
setTimeout(function() {
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
for (var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
})
}
function reject(reason) {
// 非同步回撥函式
setTimeout(function() {
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
for (var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason)
}
}
})
}
try {
executor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
if (x instanceof Promise) {
if (x.status === 'pending') { //because x could resolved by a Promise Object
x.then(function(v) {
resolvePromise(promise2, v, resolve, reject)
}, reject)
} else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
x.then(resolve, reject)
}
return
}
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then //because x.then could be a getter
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
resolve(x)
}
}
Promise.prototype.then = function(onResolved, onRejected) {
var self = this
var promise2
onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
return v
}
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
throw r
}
if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() { // 非同步執行onResolved
try {
var x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
}
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
// 非同步執行onRejected
setTimeout(function() {
try {
var x = onRejected(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
}
if (self.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(value)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(reason)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
})
}
}
複製程式碼
至此,一個promise的實現就已經寫完了,後面附上promise all和promise race的方法實現
all
var p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
console.log(results);
});
// 結果[1, 2, 3]
複製程式碼
只有所有promise全都返回的時候,才執行resolve方法
Promise.all = function(arr){
return new Promise((resolve,reject)=>{
let list=[];
arr.forEach((item)=>{
item.then((data)=>{
list.push(data);
if(arr.length == list.length){
resolve(resolvList);
}
},(reason)=>{
reject(reason);
})
})
})
}
複製程式碼
race 只要有一個成功就立即返回。
var p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.race([p1, p2, p3]).then(function (value) {
console.log(value); // 1
});
//結果 1
複製程式碼
Promise.all = function(arr){
return new Promise((resolve,reject)=>{
let list=[];
arr.forEach((item)=>{
item.then((data)=>{
resolve(resolvList);
},(reason)=>{
reject(reason);
})
})
})
}
複製程式碼