不管是標準還是實現(xiàn)损同,現(xiàn)在Javascript的重心都放在了async-await
上,Promise怎么看都像過時的東西。而且支持Promise的庫有一大堆禾锤,就算不需要這些庫,今天的瀏覽器和Node.js也已經(jīng)原生支持Promise了摹察。在這種前提下恩掷,為什么還要自己去實現(xiàn)一個Promise呢?
ES7的async-await建立在Promise上
客觀來講供嚎,由于await本身的特點黄娘,將來JS把底層API良好封裝之后,即使用戶完全不知道Promise克滴,在使用上也不會有啥問題~
然而ES7的Async-Await逼争,和Promise并不是毫不相關的競爭對手。實際上await
后面必須跟著一個Promise對象劝赔。所以深入理解Promise不會是毫無用處的事情誓焦。
Promise不需要編譯器/解釋器的支持
將來可能成為主流的async-await
,以及曾經(jīng)火過一把的generator + co
着帽,這些都是需要編譯器或者解釋器級別的支持才能使用杂伟。
而Promise,是完全可以利用語言已有特性仍翰,作為一個庫來實現(xiàn)赫粥!即使在非常原始的JS運行環(huán)境,你也可以自己實現(xiàn)一個Promise予借,而不需要等待其他人的幫助越平。
Promise是語言無關的
Promise還是獨立于語言的,如果你要給另外一種編程語言實現(xiàn)Promise灵迫,只要照葫蘆畫瓢就行了喧笔。
也就是說,掌握Promise的實現(xiàn)原理龟再,是一種回報率非常高的通用型技能书闸。而且只需要很少的投入,一百多行代碼而已利凑。(核心的其實只有幾十行)
所以浆劲,讓我們開始吧嫌术!
如何實現(xiàn)
對于Promise這種代碼量不大,但是行為復雜的程序牌借,最好的學習方法是直接看代碼度气。只看別人的解釋可能會弄得似懂非懂。
先放一個我自己的實現(xiàn)以供參考膨报,建議還在網(wǎng)上搜一下其他人的實現(xiàn)磷籍。很多論壇里面都有人寫簡單的部分Promise實現(xiàn)
,大都有借鑒價值现柠。通過看不同人的寫法院领,可以更容易理解其核心部分。我在實現(xiàn)自己的Promise時够吩,就看過了很多片段代碼比然,然后才慢慢知道該怎么做。
最核心的方法
最核心的一個方法是Promise.prototype.then
周循,實現(xiàn)了它强法,也就實現(xiàn)了Promise的一大半。如果再把Promise.all
和Promise.race
實現(xiàn)了湾笛,你基本上就實現(xiàn)了完整的Promise饮怯,因為剩下的,都是簡單的封裝而已嚎研。
then
每次調(diào)用then
硕淑,你都在創(chuàng)建一個新的Promise對象。then
就像一個鎖鏈一樣嘉赎,將前后的兩個Promise
對象連接起來置媳。
為了突出這一點,我在自己的實現(xiàn)里面公条,特意把邏輯代碼外移拇囊,下面是代碼片段
Promise.prototype.then = function(resolveFn, rejectFn) {
var pP = this
return new Promise((res, rej) => thenHandler(res, rej, resFn, rejFn, pP))
}
all
調(diào)用Promise.all
,也會返回一個新的Promise對象靶橱,all
后面的then
寥袭,是掛在這個新的Promise對象上的。
Promise.all = function(promises) {
checkArray(promises)
return new Promise((res, rej) => handleAll(promises, res, rej))
}
pending, fulfilled, rejected
要理解Promise关霸,另一個關鍵在于理解Promise的「狀態(tài)」传黄。一個Promise對象是有三種狀態(tài)的, pending
队寇,fulfilled
和rejected
膘掰。為什么要有狀態(tài)?為了分情況處理。
先舉一個例子识埋,假如我定義一個Proimse凡伊,但是不給它綁定then
var x = myReadFile("/tmp/text.txt")
這條語句在運行的時候,那個文件的內(nèi)容其實已經(jīng)在某個時間點讀出來了窒舟。一直緩存在某個地方系忙。吃完飯我們再來運行:
x.then(console.log)
//> Promise { <pending> }
// blahblah..., the content of /tmp/text.txt
數(shù)據(jù)全都出來了!因為then
會對Promise對象的「狀態(tài)」進行判斷惠豺。如果是pending
狀態(tài)银还,就把將要運行的函數(shù)存到Promise對象的一個數(shù)組里面,如果是fulfilled
狀態(tài)洁墙,也就是那個Promise的resolve
已經(jīng)被運行了蛹疯,那么就直接調(diào)用then
傳來的函數(shù)。
這就是狀態(tài)存在的意義扫俺。
然后就可以直接看代碼了苍苞,需要的就是適當?shù)哪托墓毯玻瑯酚^的態(tài)度~
如果讀者對Promise不了解狼纬,想知道它運行的一些特點,那么可以繼續(xù)往下看骂际。
Promise的運行機理
接下來疗琉,我們通過兩個例子來探索Promise的運行過程,為了減少重復代碼歉铝,我們先定義一個函數(shù)盈简,這個函數(shù)會返回一個Promise對象,這就是一切的開端太示。
function myReadFile(filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, (e, d) => e ? reject(e) : resolve(d.toString()))
})
}
使用Promise柠贤,代碼經(jīng)常看上去會是這個樣子的
myReadFile("theFirst.json")
.then(JSON.parse)
.then(fn1)
.then(fn2)
.then(fn3)
.catch(console.error.bind(console))
上面這個例子中类缤,真正能夠異步的臼勉,只有第一步而已。一旦那個resolve
被調(diào)用餐弱,后面的一連串都會順著執(zhí)行宴霸。就像多米諾骨牌一樣。
那么如果中間有另一個地方需要異步怎么辦呢膏蚓,比如我需要讀取另外一個文件瓢谢? 你只需要在某個傳給then
的函數(shù)里面,返回一個新的Promise對象就行驮瞧。
myReadFile("theFirst.json")
.then(JSON.parse)
.then(fn1)
.then(d => myReadFile("theSecond.json"))
.then(JSON.parse)
.then(fn3)
.catch(console.error.bind(console))
上面這個例子中氓扛,fn3
處理的是theSecond.json
文件的內(nèi)容。
這一點可能理解上有點別扭论笔,大概需要實現(xiàn)了Promise之后幢尚,才能清楚其中的貓膩破停。
關于Promise,我能想到的需要注意的尉剩,暫時就這么多了真慢。以后如果有新的點子,會繼續(xù)放上來理茎。
Promise的缺點
我對Promise非常喜愛黑界,但是它的確有缺點,比如一些中間變量無法共用皂林,我們拿同步
例子來做個對比
var a = readFileSync("blahblah.txt")
var b = fn1(a)
var c = fn2(b)
var d = fn3(c)
console.log(a, b, c, d)
而使用Promise的異步則是這個情況
myReadFile("blahblah.txt")
.then(fn1)
.then(fn2)
.then(fn3)
.then(d => console.log(d))
其中fn1
的返回值只有fn2
能獲取朗鸠,fn2
的返回值只有fn3
能獲取…… 如果需要像同步版本那樣,獲取所有中間值础倍,就必須把它們存為全局或者上層閉包變量烛占。
但是這個小小的缺點不太要緊。瑕不掩瑜沟启,Promise徹底解決了callback hell
忆家,讓我對Javascript另眼相看。
后記
隨著對Promise使用時間的增長德迹,我意識到了Promise的一些其他優(yōu)點芽卿。它不僅僅是解決了回調(diào)的“代碼金字塔”問題。應該說胳搞,回調(diào)帶來的真正問題卸例,并不是代碼不停往右邊延展,而是你不能以正常的「函數(shù)」概念來思考問題肌毅。
什么是函數(shù)筷转?我記得以前數(shù)學老師向我們解釋:函數(shù)就是一個工廠,你給一個毛胚進去悬而,它變出一個產(chǎn)品呜舒。
當時我還沒有編程的概念,當我學會編程之后摊滔,更加贊同那個樸實的比喻了阴绢。函數(shù)這東西,就是做轉(zhuǎn)換艰躺,它不僅要有輸入呻袭,還要有輸出。
而回調(diào)腺兴,是“沒有”輸出的左电。
它當然有輸出,只不過很別扭,因為它不是作為返回值呈現(xiàn)篓足,而是通過你傳給它的另一個輸入(回調(diào)函數(shù))來處理段誊。是不是隱約找到當年C語言那堆庫函數(shù)在你心中留下的傷疤。
Promise重新賦予了我們“正痴煌希”的函數(shù)连舍,這是它更重要的意義。