函数式编程之Promise的奇幻漂流
上一篇我們講了同步鏈?zhǔn)教幚頂?shù)據(jù)函子的概念。這一節(jié),我們來(lái)講異步。用到的概念很簡(jiǎn)單,不需要有函數(shù)式編程的基礎(chǔ)。當(dāng)然如果你看了那篇 《在你身邊你左右 --函數(shù)式編程別煩惱》 會(huì)更容易理解。這一篇我們會(huì)完成一個(gè)Promise代碼的編寫(xiě)。本文會(huì)從實(shí)現(xiàn)一個(gè)只有十幾行代碼能夠解決異步鏈?zhǔn)秸{(diào)用問(wèn)題的簡(jiǎn)單的Promise開(kāi)始。然后逐漸完善增加功能。
- 實(shí)現(xiàn)簡(jiǎn)單的異步Promise函子
- 能夠同時(shí)調(diào)用同一Promise函子
- 增加reject回調(diào)函數(shù)
- 增加Promise狀態(tài)
本文代碼在我的github
1 實(shí)現(xiàn)簡(jiǎn)單的Promise函子
我們先來(lái)回顧一下同步鏈?zhǔn)秸{(diào)用。
class Functor{constructor (value) {this.value = value ;} map (fn) {return Functor.of(fn(this.value))}} Functor.of = function (val) {return new Functor(val); }Functor.of(100).map(add1).map(add1).map(minus10)// var a = Functor.of(100); // var b = a.map(add1); // var c = b.map(add1); // var d = c.map(minus10);復(fù)制代碼- 函子的核心就是每個(gè)functor都是一個(gè)新的對(duì)象
- 通過(guò)map中傳遞進(jìn)去的函數(shù)fn去處理數(shù)據(jù)
- 用得到的值去生成新的函子
那么如果當(dāng)a的值是異步產(chǎn)生的,我們?cè)摵稳鐐魅雝his.value值呢?
function executor(resolve){setTimeout(()=>{resolve(100)},500) } 復(fù)制代碼我們模擬一下通過(guò)setTimeout500毫秒后拿到數(shù)據(jù)100。其實(shí)也很簡(jiǎn)單,我們可以傳進(jìn)去一個(gè)resolve回調(diào)函數(shù)去處理這個(gè)數(shù)據(jù)。
class Functor {constructor (executor) {let _this = this;this.value = undefined;function resolve(value){_this.value = value;}executor(resolve)} }var a = new Functor(executor);復(fù)制代碼- 我們講executor傳入并立即執(zhí)行
- 在resolve回調(diào)函數(shù)中我們能夠拿到value值
- 我們定義resolve回調(diào)函數(shù)講value的值賦給this.value
這樣我們就輕松的完成了a這個(gè)對(duì)象的賦值。那么我們?cè)趺从梅椒ㄈヌ幚磉@個(gè)數(shù)據(jù)呢?
- 顯然在拿到回調(diào)函數(shù)值之后,我們應(yīng)該能讓map里的fn去繼續(xù)處理數(shù)據(jù)
- 處理完這個(gè)數(shù)據(jù),我們交給下一個(gè)函數(shù)的resolve去繼續(xù)處理
- 所以我們定義了一個(gè)callback函數(shù),
- 在調(diào)用map時(shí),將就包含fn處理數(shù)據(jù),和執(zhí)行下一個(gè)對(duì)象的resolve的函數(shù)賦值給它
- 然后在自己的resolve拿到值之后,我們執(zhí)行這個(gè)callback
現(xiàn)在我們已經(jīng)實(shí)現(xiàn)了異步的鏈?zhǔn)秸{(diào)用,我們來(lái)具體分析一下,都發(fā)生了什么。
- (1)a = new Functor(executor)的時(shí)候,我們進(jìn)行了初始化, executor(resolve)開(kāi)始執(zhí)行
- (2)b =a.map(add1)的時(shí),先進(jìn)行了初始化 new Functor(),然后執(zhí)行 executor(resolve)
- (3)b中executor(resolve)執(zhí)行結(jié)束,將一個(gè)函數(shù)賦值a中的callback
注意:這時(shí)map中this指向的是a函子,但是 new Functor((resolve) => {}中resolve是B的
- (4)最后return 一個(gè)新的函子b
- (5)c =b.map(add1)的時(shí),同樣,給b中的callback賦值
- (6)然后返回一個(gè)新的函子c,此時(shí)沒(méi)有map的調(diào)用,c中的callback就是null
我們?cè)賮?lái)分析一下異步結(jié)束之后,回調(diào)函數(shù)中的resolve是如何執(zhí)行的。
- (1)resolve 先_this.value = value;把a(bǔ)中的value進(jìn)行修改
- (2)在執(zhí)行_this.callback(),先let data = fn(self.value) 計(jì)算出處理后的data
- (3)調(diào)用b中的resolve函數(shù)繼續(xù)處理
- (4)b中也是,先給value賦值,然后處理數(shù)據(jù)
- (5)再調(diào)用c中的resolve,并把處理好的數(shù)據(jù)傳給他
- (6)先給C中value賦值,然后再處理數(shù)據(jù),最后調(diào)用callback時(shí)因?yàn)椴皇呛瘮?shù)會(huì)報(bào)錯(cuò),之后我們會(huì)解決
本節(jié)代碼:promise1.js
嗯,這就是promise作為函子實(shí)現(xiàn)的處理異步操作的基本原理。它已經(jīng)能夠解決了簡(jiǎn)單的異步調(diào)用問(wèn)題。雖然代碼不多,但這是promise處理異步調(diào)用的核心。接下來(lái)我們會(huì)不斷繼續(xù)實(shí)現(xiàn)其他功能。
2 同時(shí)調(diào)用同一個(gè)Promise函子
如果我們像下面同時(shí)調(diào)用a這個(gè)函子。你會(huì)發(fā)現(xiàn),它實(shí)際上只執(zhí)行了c。
var a = new Functor(executor); var b = a.map(add); var c = a.map(minus); 復(fù)制代碼原因很簡(jiǎn)單,因?yàn)樯厦嫖覀儗W(xué)過(guò),b先給a的callback賦值,然后c又給a的callback賦值。所以把b給覆蓋掉了就不會(huì)執(zhí)行啦。解決這個(gè)問(wèn)題很簡(jiǎn)單,我們只需要讓callback變成一個(gè)數(shù)組就解決啦。
class MyPromise {constructor (executor) {let _this = this;this.value = undefined;this.callbacks = [];function resolve(value){_this.value = value;_this.callbacks.forEach(item => item())}executor(resolve)} then (fn) {return new MyPromise((resolve) => {this.callbacks.push (()=>{let data = fn(this.value) console.log(data) resolve(data)})})} }var a = new MyPromise(executor); var b = a.then(add).then(minus); var c = a.then(minus);復(fù)制代碼- 我們定義了callbacks數(shù)組,每次的調(diào)用a的then方法時(shí)。都將其存到callbacks數(shù)組中。
- 當(dāng)回調(diào)函數(shù)拿到值時(shí),在resolve中遍歷執(zhí)行每個(gè)函數(shù)。
- 如果callbacks是空,forEach就不會(huì)執(zhí)行,這也解決了之前把錯(cuò)的問(wèn)題
- 然后我們進(jìn)一步改了函子的名字(MyPromise),將map改成then
- 簡(jiǎn)化了return中,let self = this;
3 增加reject回調(diào)函數(shù)
我們都知道,在異步調(diào)用的時(shí)候,我們往往不能拿到數(shù)據(jù),返回一個(gè)錯(cuò)誤的信息。這一小節(jié),我們對(duì)錯(cuò)誤進(jìn)行處理。
function executor(resolve,reject){fs.readFile('./data.txt',(err, data)=>{if(err){ console.log(err)reject(err)}else {resolve(data)}}) } 復(fù)制代碼- 我們現(xiàn)在用node異步讀取一個(gè)文件
- 成功執(zhí)行 resolve(data),失敗執(zhí)行 reject(err)
現(xiàn)在我們定義出這個(gè)reject
class MyPromise {constructor (executor) {let _this = this;this.value = undefined;this.reason = undefined;this.onResolvedCallbacks = [];this.onRejectedCallbacks = [];function resolve(value){_this.value = value;_this.onResolvedCallbacks.forEach(item => item())}function reject(reason){_this.reason = reason;_this.onRejectedCallbacks.forEach(item => item());}executor(resolve, reject);} then (fn,fn2) {return new MyPromise((resolve,reject) => {this.onResolvedCallbacks.push (()=>{let data = fn(this.value) console.log(data) resolve(data)})this.onRejectedCallbacks.push (()=>{let reason = fn2(this.reason) console.log(reason) reject(reason)})})} } 復(fù)制代碼- 其實(shí)很簡(jiǎn)單,就是我們就是在executor多傳遞進(jìn)去一個(gè)reject
- 根據(jù)異步執(zhí)行的結(jié)果去判斷執(zhí)行resolve,還是reject
- 然后我們?cè)贛yPromise為reject定義出和resolve同樣的方法
- 然后我們?cè)趖hen的時(shí)候應(yīng)該傳進(jìn)去兩個(gè)參數(shù),fn,fn2
本節(jié)代碼:promise3.js
這時(shí)候?qū)xecutor函數(shù)封裝到asyncReadFile異步讀取文件的函數(shù)
function asyncReadFile(url){return new MyPromise((resolve,reject) => {fs.readFile(url, (err, data) => {if(err){ console.log(err)reject(err)}else {resolve(data)}})}) } var a = asyncReadFile('./data.txt'); a.then(add,mismanage).then(minus,mismanage); 復(fù)制代碼這就是我們平時(shí)封裝異步Promise函數(shù)的過(guò)程。但這是過(guò)程有沒(méi)有覺(jué)得在哪見(jiàn)過(guò)。如果之前executor中的'./data.txt'我們是通過(guò)參數(shù)傳進(jìn)去的那么這個(gè)過(guò)程不就是上一節(jié)我們提到的柯里化。
本節(jié)代碼:promise4.js
我們?cè)賮?lái)總結(jié)一下上面的過(guò)程。
- 我們先進(jìn)行了初始化,去執(zhí)行傳進(jìn)來(lái)的 executor函數(shù),并把處理的函數(shù)push進(jìn)入callback數(shù)組中
- 在reslove或reject執(zhí)行時(shí),我們?nèi)?zhí)行callback中的函數(shù)
- 我們可以看到同樣一個(gè)函子a在不同時(shí)期有著不一樣的狀態(tài)。
- 顯然如果在reslove()或者 reject( )之后我們?cè)偬砑觮hen()方法是不會(huì)有作用的
那么我們?nèi)绾谓鉀Qreslove之后a函子的then調(diào)用問(wèn)題呢,其實(shí)reslove之后,我們已經(jīng)有了value值,那不就是我們最開(kāi)始講的普通函子的鏈?zhǔn)秸{(diào)用嗎?所以現(xiàn)在我們只需要標(biāo)記出,函子此時(shí)的狀態(tài),再?zèng)Q定如何調(diào)用then就好啦
4 增加Promise狀態(tài)
- 我們定義進(jìn)行中的狀態(tài)為pending
- 已成功執(zhí)行后為fulfilled
- 失敗為rejected
我們分析一下上面這個(gè)過(guò)程
其實(shí)就多了一個(gè)參數(shù),然后判斷了一下,很簡(jiǎn)單。那么我們現(xiàn)在來(lái)分析一下,當(dāng)我們調(diào)用fulfilled狀態(tài)下的a的執(zhí)行過(guò)程
setTimeout(()=>{ d = a.then(add);} ,2000) value:"1" 復(fù)制代碼- (1)先執(zhí)行new MyPromise(),初始化d
- (2)然后執(zhí)行 executor(resolve, reject);fn開(kāi)始執(zhí)行,算出新的值x
- (3)傳給d的resolve執(zhí)行,
- (4)修改stauts和value的狀態(tài)
- (5)return 出新的函子,可以繼續(xù)鏈?zhǔn)秸{(diào)用
我們來(lái)想一個(gè)問(wèn)題,如果(2)中fn是一個(gè)異步操作,d后邊繼續(xù)調(diào)用then方法,此刻pending狀態(tài)就不會(huì)改變,直到resolve執(zhí)行。那么then的方法就會(huì)加到callback上。就又回到我們之前處理異步的狀態(tài)啦。所以這就是為什么Promise能夠解決回調(diào)地獄
參考代碼:promise5.js
好了,我們現(xiàn)在來(lái)看傳進(jìn)去的方法fn(this.value) ,我們需要用上篇講的Maybe函子去過(guò)濾一下。
5 Maybe函子優(yōu)化
then (onResolved,onRejected) {onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}return new MyPromise((resolve,reject) => {if(this.status === 'pending'){this.onResolvedCallbacks.push (()=>{let x = onResolved(this.value) resolve(x)})this.onRejectedCallbacks.push (()=>{let x = onRejected(this.reason)reject(x)})}if(this.status === 'fulfilled'){let x = onResolved(this.value)resolve(x)}if(this.status === 'rejected'){let x = onRejected(this.value)reject(x)}})} 復(fù)制代碼- Maybe函子很簡(jiǎn)單,對(duì)onResolved和onRejected進(jìn)行一下過(guò)濾
參考代碼:promise6.js
這一篇先寫(xiě)到這里吧。最后總結(jié)一下,Promise的功能很強(qiáng)大,就是少年派的奇幻漂流一樣。雖然旅程絢爛多彩,但始終陪伴你的只有那只老虎。Promise也是一樣,只要掌握其核心函子的概念,其他問(wèn)題就比較好理解啦。這里只實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的Promise,更強(qiáng)大的功能,我們慢慢加吧。
總結(jié)
以上是生活随笔為你收集整理的函数式编程之Promise的奇幻漂流的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 上市公司9月23日晚间公告速递
- 下一篇: topcoder使用