為什么我們需要promise
我們的程序常常需要處理一些io請(qǐng)求,不同的語(yǔ)言有不同的io模型。比如在python中,當(dāng)你的代碼去讀文件的時(shí)候,如果你沒有使用并發(fā)技術(shù),代碼會(huì)被阻塞,直到文件被讀出來。所以下面這段代碼可以正確輸出文件內(nèi)容
with open ("data.txt", "r") as myfile:
data=myfile.readlines()
print data
但是在Nodejs中,我們使用的是異步io。盡管nodejs是單線程的,但是異步io使得我們的程序不會(huì)被io阻塞,這對(duì)于執(zhí)行效率非常有幫助。
var fs = require('fs')
var filecontent;
fs.readFile('data.txt','utf-8', function(err, content){
filecontent = content
})
console.log(filecontent) //undefined
上面的代碼執(zhí)行結(jié)果為undefined,這是由于fs.readFile發(fā)出了一個(gè)讀文件的調(diào)用后,就直接返回了,而這個(gè)時(shí)候filecontent并沒有被初始化,而當(dāng)filecontent被初始化的時(shí)候,代碼早就不知道跑到哪里去了。
eventProxy
異步編程給我們帶來了非常多的好處,尤其是在程序性能方面,但同時(shí),它也帶來了一些編程上的困難。
asyncProcess1(args1, function(){
asyncProcess2(args2, function(){
asyncProcess3(args3, function(){
...
})
})
})
傳說中的回調(diào)地獄。
在我之前寫的一篇關(guān)于eventproxy的文章中,我們已經(jīng)找到了一些處理異步編程的方法。
asyncProcess1(args1, function(){ep.emit('get1')})
ep.on('get1', function(){
asyncProcess2(args2, function(){ep.emit('get2')})
})
ep.on('get2', function(){
asyncProcess3(args3, function(){ep.emit('get3')})
})
ep.on('get3', function(){
...
})
看上去優(yōu)雅了很多吧。上面的代碼給我們一些啟示,我們可以把io操作和io操作結(jié)束后的后續(xù)動(dòng)作分開來,通過事件綁定的方法來處理異步io,當(dāng)io操作返回的同時(shí),發(fā)出事件并執(zhí)行回調(diào)。
promise
在很多時(shí)候,eventProxy完全可以滿足我們的需求,它能夠很好的幫助我們解決回調(diào)地獄問題,但是在有些方面他還是不夠好,比如錯(cuò)誤處理,比如多異步同時(shí)處理等等。我們可以通過擴(kuò)展我們的eventProxy庫(kù)來得到更好的編程體驗(yàn),但是還是不夠直觀。幸好,我們有promise.
下面我們以網(wǎng)絡(luò)請(qǐng)求為例,來看看promise如何強(qiáng)大。
var http = require('http')
const baseUrl = "http://httpbin.org/get"
http.get(baseUrl+"?title=1", function(res){
res.setEncoding('utf8');
res.on('data', function(chunk){
console.log(chunk)
})
res.on('error', function(e){
console.log(e)
})
})
上面是一段基本的get請(qǐng)求,在回調(diào)函數(shù)中,我們獲得了請(qǐng)求的返回結(jié)果。下面我們用promise來改造它。
function loadData(str){
return new Promise(function(resolve, reject){
http.get(baseUrl+str, function(res){
res.setEncoding('utf8');
res.on('data', function(chunk){
resolve(chunk)
})
res.on('error', function(e){
console.log(e)
})
})
})
}
loadData("?title=1").then(function(response){
console.log(response)
}).catch(function(e){
console.log(e)
})
我們創(chuàng)建了一個(gè)loadData函數(shù),而這個(gè)函數(shù)返回了一個(gè)promise對(duì)象,promise對(duì)象有兩個(gè)方法,分別是catch和then。then給promise對(duì)象提供resolve函數(shù),當(dāng)promise執(zhí)行的結(jié)果正確返回時(shí)執(zhí)行resolve;catch給promise對(duì)象提供reject函數(shù),當(dāng)promise執(zhí)行結(jié)果沒有正確返回時(shí)執(zhí)行reject。
但是,看不出這樣寫有什么牛逼的地方啊。現(xiàn)在,假設(shè)我們要請(qǐng)求三次網(wǎng)絡(luò),并且,必須要有順序的處理。如果用最原始的回調(diào)函數(shù)寫,我們至少需要三層嵌套。但是現(xiàn)在,我們有了promise
loadData("?title=1").then(function(response){
console.log(response)
return loadData("?title=2")
}).then(function(response){
console.log(response)
return loadData("?title=3")
}).then(function(response){
console.log(response)
}).catch(e){
//handle error
}
簡(jiǎn)直太簡(jiǎn)單,太直觀,太優(yōu)雅了有木有,而且,錯(cuò)誤處理被集中了起來,砍掉一堆代碼有木有。
promise.all
有時(shí)候,我們同時(shí)發(fā)出幾個(gè)網(wǎng)絡(luò)請(qǐng)求,但是對(duì)返回順序并沒有要求,我們可以用promise的all方法,讓我們寫出更美的代碼
Promise.all(
[loadData("?title=1"),
loadData("?title=2"),
loadData("?title=3")
]).then(function(responses){
console.log(responses[0]);
console.log(responses[1]);
console.log(responses[2]);
})