首先,閱讀一下這兩篇文章。
通過上面兩篇文章簡單瞭解了Promise後,再來思考一下
why promise?
假設我們有一個需求,執行一個ajax請求,當請求成功後,再次執行一個ajax請求,程式碼實現可以這樣寫(以jquery為例)
$.get(url1,function(data){
console.log('第一個ajax執行成功')
$.get(url2,function(data){
console.log('第二個ajax執行成功')
})
})
複製程式碼
當我們需要在回撥裡執行第二次非同步操作時,可以這樣寫,那如果我們的需求是需要連續十次執行,每次非同步操作的執行都是在上一個非同步操作的回撥中執行,難道還要層層巢狀的寫嗎?當然不是,這樣寫的話會讓程式碼非常不優雅,並且耦合度很大。 到這裡,就輪到今天的主角-Promise登場了! 參考阮一峰大神的文章,可以看到Promise的大概實現如下
fn().then(fn1).then(fn2) //fn執行完後執行fn1,fn1執行完後執行fn2
複製程式碼
把上面提到的需求帶進來,那就可以使用鏈式操作來完成我們的需求,可以看出,promise能夠將非同步的程式碼用同步的方式表示出來
Promise的簡單示例我們可以參考MDN的簡單示例
var myFirstPromise = new Promise(function(resolve, reject){
//當非同步程式碼執行成功時,我們才會呼叫resolve(...), 當非同步程式碼失敗時就會呼叫reject(...)
//在本例中,我們使用setTimeout(...)來模擬非同步程式碼,實際編碼時可能是XHR請求或是HTML5的一些API方法.
setTimeout(function(){
resolve("成功!"); //程式碼正常執行!
}, 250);
});
myFirstPromise.then(function(successMessage){
//successMessage的值是上面呼叫resolve(...)方法傳入的值.
//successMessage引數不一定非要是字串型別,這裡只是舉個例子
console.log("Yay! " + successMessage);
});
複製程式碼
接下來,我們自己動手寫一個簡單的Promise來對其原理有一個初步的認識。
何從下手?從使用開始
編寫程式碼之前,先寫一個簡單的promise的執行語句
fn().then(fn1).then(fn2)
複製程式碼
參考官方文件,我們知道,then是promise物件的方法,這句程式碼能夠鏈式呼叫,就說明fn最後返回的是一個promise物件,有頭緒了!繼續完善我們的程式碼
var p = new Promise() //建立一個promise物件,作為fn的返回
function fn() {
console.log(1)
setTimeout(function(){
p.resolve("成功!") //程式碼正常執行!這裡執行非同步回撥
},1000)
return p
}
fn().then(fn1).then(fn2)
複製程式碼
then方法包含兩個引數:onfulfilled 和 onrejected,它們都是 Function 型別。當Promise狀態為fulfilled時,呼叫 then 的 onfulfilled 方法,當Promise狀態為rejected時,呼叫 then 的 onrejected 方法,這裡我們假設每次非同步都是執行成功的,暫時只傳fulfilled一個函式作為引數,接下來,完善Promise物件,這裡我們把Promise物件的onfulfilled 方法的屬性名設為resolve,onrejected 方法的屬性名設為reject,完善Promise
function Promise(){
}
Promise.prototype = {
then: function(onfulfilled, onrejected){
//then方法包含的兩個方法引數
this.resolve = onfulfilled
this.reject = onrejected
return this //then也可以鏈式呼叫,所以執行完成後返回promise例項物件
},
resolve: function(result){
// 回撥成功執行的方法
},
reject: function(result){
// 回撥失敗執行的方法
}
}
var p = new Promise() //建立一個promise物件,作為fn的返回
function fn() {
console.log(1)
setTimeout(function(){
p.resolve("成功!") //程式碼正常執行!這裡執行非同步回撥
},1000)
return p
}
fn().then(fn1).then(fn2)
複製程式碼
等等,貌似哪裡不對?當我們語句中包含了兩個then語句後,那就需要兩個function放到任務佇列中等待執行啊,如果這樣寫,後面的then的function豈不是會覆蓋前面一個then的function嗎?思考一下,我們可以把待執行的函式放在陣列裡順序執行呀!繼續修改程式碼
function Promise(){
this.callbacks = []
}
Promise.prototype = {
then: function(onfulfilled, onrejected){
this.callbacks.push({
resolve: onfulfilled,
reject: onrejected
})
return this //then也可以鏈式呼叫,所以執行完成後返回promise例項物件
},
resolve: function(result){
// 回撥成功執行的方法
var callbackObj = this.callbacks.shift()
callbackObj['resolve'](result)
},
reject: function(result){
// 回撥失敗執行的方法
var callbackObj = this.callbacks.shift()
callbackObj['reject'](result)
}
}
var p = new Promise()
function fn() {
console.log(1)
setTimeout(function(){
p.resolve("成功!") //程式碼正常執行!這裡執行非同步回撥
},1000)
return p
}
fn().then(fn1).then(fn2)
複製程式碼
修改完成,看看還有哪裡可以優化的地方?咦?resolve和reject的函式執行長得好像啊,是不是可以提取出公共部分來執行呢?繼續修改,順便完善一下fn1和fn2函式
function Promise(){
this.callbacks = []
}
Promise.prototype = {
then: function(onfulfilled, onrejected){
this.callbacks.push({
resolve: onfulfilled,
reject: onrejected
})
return this //then也可以鏈式呼叫,所以執行完成後返回promise例項物件
},
resolve: function(result){
// 回撥成功執行的方法
this.complete('resolve', result)
},
reject: function(result){
// 回撥失敗執行的方法
this.complete('reject', result)
},
complete: function(type, result){
var callbackObj = this.callbacks.shift()
callbackObj[type](result)
}
}
var p = new Promise()
function fn() {
console.log('我是立即執行語句~')
setTimeout(function(){
p.resolve("成功!") //程式碼正常執行!這裡執行非同步回撥
},1000)
return p
}
function fn1(result) {
console.log('fn1', result)
setTimeout(function() {
p.resolve('data2')
}, 2000)
}
function fn2(result) {
console.log('fn2', result)
}
fn().then(fn1).then(fn2)
複製程式碼
初步完成,把程式碼放到html中執行,效果如下
是不是還差了點兒什麼?
再回頭看看MDN文件,我們發現Promise的原型上有兩個方法,then和catch,then我們已經實現了,接下來再實現一個簡單的catch。回到我們的程式碼,當我們執行fn失敗時,可以通過fn().then(successFunction,errorFunction)的形式來執行錯誤處理函式errorFunction,但是到這一步已經出錯了,正常的邏輯是應該直接結束後續的回撥,跳出這個流程,而我們目前完成的程式碼貌似還是會繼續往後執行。。。再改改!
function Promise(){
this.callbacks = []
this.oncatch = null //定義一個oncatch屬性獲取錯誤處理方法
}
Promise.prototype = {
then: function(onfulfilled, onrejected){
this.callbacks.push({
resolve: onfulfilled,
reject: onrejected
})
return this //then也可以鏈式呼叫,所以執行完成後返回promise例項物件
},
resolve: function(result){
// 回撥成功執行的方法
this.complete('resolve', result)
},
reject: function(result){
// 回撥失敗執行的方法
this.complete('reject', result)
},
complete: function(type, result){
// 此處增加一個錯誤處理判斷
if (type === 'reject' && this.oncatch) {
this.callbacks = []
this.oncatch(result)
} else if (this.callbacks[0]) {
var callbackObj = this.callbacks.shift()
if(callbackObj[type]) callbackObj[type](result)
}
},
catch: function(onfail){
this.oncatch = onfail
return this
}
}
var p = new Promise()
function fn() {
console.log('我是立即執行語句~')
setTimeout(function(){
p.reject("失敗!") //程式碼正常執行!這裡執行非同步回撥
},1000)
return p
}
function fn1(result) {
console.log('fn1', result)
setTimeout(function() {
p.resolve('data2')
}, 2000)
}
function fn2(result) {
console.log('fn2', result)
}
function errCatch(result) { //定義錯誤處理函式
console.log('err', result)
}
fn().then(fn1).then(fn2).catch(errCatch)
複製程式碼
至此,我們完成了一個簡單的promise實現,來看看錯誤獲取的效果
可以看到,fn1和fn2並沒有執行!大功告成!
簡單的Promise物件已經完成了,來實戰操(zhuang)作(b)一波! 1.將Promise作為一個common.js規範模組來引用 Promise.js
function Promise() {
this.callbacks = []
this.oncatch = null
}
Promise.prototype = {
then: function(onfulfilled, onrejected) {
this.callbacks.push({
resolve: onfulfilled,
reject: onrejected
})
return this //then也可以鏈式呼叫,所以執行完成後返回promise例項物件
},
resolve: function(result) {
// 回撥成功執行的方法
this.complete('resolve', result)
},
reject: function(result) {
// 回撥失敗執行的方法
this.complete('reject', result)
},
complete: function(type, result) {
if (type === 'reject' && this.oncatch) {
this.callbacks = []
this.oncatch(result)
} else if (this.callbacks[0]) {
var callbackObj = this.callbacks.shift()
if (callbackObj[type]) callbackObj[type](result)
}
},
catch: function(onfail) {
this.oncatch = onfail
return this
}
}
module.exports = Promise
複製程式碼
2.新建3個txt檔案,內容隨意,再新建一個test.js檔案
var Promise = require('./Promise')
var fs = require('fs')
var p = new Promise
var str = ''
function readFile(path){
fs.readFile(path, 'utf-8', function(err, str){
if(err){
p.reject(path)
}else{
p.resolve(str)
}
})
return p
}
readFile('a.txt').then(function(line){
str += line
console.log('讀取a...')
readFile('b.txt')
}).then(function(line){
str += line
console.log('讀取b...')
readFile('c.txt')
}).then(function(line){
str += line
console.log('讀取c...')
p.resolve(str)
}).then(function(result){
console.log(result)
}).catch(function(){
console.log('err')
})
複製程式碼
目錄結構如下
在node環境下執行test.js,效果如下
再把某個讀取步驟的路徑改為一個不存在的檔案試試
至此,一個簡單的promise實現完成,如有錯誤,歡迎指正!拜拜