函數式編程之Promise的奇幻漂流

上一篇咱們講了同步鏈式處理數據函子的概念。這一節,咱們來說異步。用到的概念很簡單,不須要有函數式編程的基礎。固然若是你看了那篇 《在你身邊你左右 --函數式編程別煩惱》 會更容易理解。這一篇咱們會完成一個Promise代碼的編寫。本文會從實現一個只有十幾行代碼可以解決異步鏈式調用問題的簡單的Promise開始。而後逐漸完善增長功能。node

  • 實現簡單的異步Promise函子
  • 可以同時調用同一Promise函子
  • 增長reject回調函數
  • 增長Promise狀態

本文代碼在個人github git

1 實現簡單的Promise函子

咱們先來回顧一下同步鏈式調用。github

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);

複製代碼

  • 函子的核心就是每一個functor都是一個新的對象
  • 經過map中傳遞進去的函數fn去處理數據
  • 用獲得的值去生成新的函子

那麼若是當a的值是異步產生的,咱們該何如傳入this.value值呢?編程

function executor(resolve){
  setTimeout(()=>{resolve(100)},500)
}
複製代碼

咱們模擬一下經過setTimeout500毫秒後拿到數據100。其實也很簡單,咱們能夠傳進去一個resolve回調函數去處理這個數據。數組

class Functor {
   constructor (executor) {
      let _this = this;
      this.value = undefined;

      function resolve(value){
          _this.value = value;
      }
      executor(resolve)
   } 
}

var a = new Functor(executor);

複製代碼
  • 咱們講executor傳入並當即執行
  • 在resolve回調函數中咱們可以拿到value值
  • 咱們定義resolve回調函數講value的值賦給this.value

這樣咱們就輕鬆的完成了a這個對象的賦值。那麼咱們怎麼用方法去處理這個數據呢?promise

  • 顯然在拿到回調函數值以後,咱們應該能讓map裏的fn去繼續處理數據
  • 處理完這個數據,咱們交給下一個函數的resolve去繼續處理
  • 因此咱們定義了一個callback函數,
  • 在調用map時,將就包含fn處理數據,和執行下一個對象的resolve的函數賦值給它
  • 而後在本身的resolve拿到值以後,咱們執行這個callback
class Functor {
   constructor (executor) {
      let _this = this;
      this.value = undefined;
      this.callback = null;
      function resolve(value){
          _this.value = value;
          _this.callback()
      }
      executor(resolve)
   } 
  
   map (fn) {
       let  self = this;
       return new Functor((resolve) => {
          self.callback = function(){
              let data =  fn(self.value)   
              resolve(data)
           }
       })
   }    
}
new Functor(executor).map(add1).map(add1)
複製代碼

如今咱們已經實現了異步的鏈式調用,咱們來具體分析一下,都發生了什麼。bash

  • (1)a = new Functor(executor)的時候,咱們進行了初始化, executor(resolve)開始執行
  • (2)b =a.map(add1)的時,先進行了初始化 new Functor(),而後執行 executor(resolve)
  • (3)b中executor(resolve)執行結束,將一個函數賦值a中的callback

注意:這時map中this指向的是a函子,可是 new Functor((resolve) => {}中resolve是B的異步

  • (4)最後return 一個新的函子b
  • (5)c =b.map(add1)的時,一樣,給b中的callback賦值
  • (6)而後返回一個新的函子c,此時沒有map的調用,c中的callback就是null

咱們再來分析一下異步結束以後,回調函數中的resolve是如何執行的。async

  • (1)resolve 先_this.value = value;把a中的value進行修改
  • (2)在執行_this.callback(),先let data = fn(self.value) 計算出處理後的data
  • (3)調用b中的resolve函數繼續處理
  • (4)b中也是,先給value賦值,而後處理數據
  • (5)再調用c中的resolve,並把處理好的數據傳給他
  • (6)先給C中value賦值,而後再處理數據,最後調用callback時由於不是函數會報錯,以後咱們會解決

本節代碼:promise1.js函數式編程

嗯,這就是promise做爲函子實現的處理異步操做的基本原理。它已經可以解決了簡單的異步調用問題。雖然代碼很少,但這是promise處理異步調用的核心。接下來咱們會不斷繼續實現其餘功能。

2 同時調用同一個Promise函子

若是咱們像下面同時調用a這個函子。你會發現,它實際上只執行了c。

var a = new Functor(executor);
var b = a.map(add);
var c = a.map(minus);
複製代碼

緣由很簡單,由於上面咱們學過,b先給a的callback賦值,而後c又給a的callback賦值。因此把b給覆蓋掉了就不會執行啦。解決這個問題很簡單,咱們只須要讓callback變成一個數組就解決啦。

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);

複製代碼
  • 咱們定義了callbacks數組,每次的調用a的then方法時。都將其存到callbacks數組中。
  • 當回調函數拿到值時,在resolve中遍歷執行每一個函數。
  • 若是callbacks是空,forEach就不會執行,這也解決了以前把錯的問題
  • 而後咱們進一步改了函子的名字(MyPromise),將map改爲then
  • 簡化了return中,let self = this;

3 增長reject回調函數

咱們都知道,在異步調用的時候,咱們每每不能拿到數據,返回一個錯誤的信息。這一小節,咱們對錯誤進行處理。

function executor(resolve,reject){
  fs.readFile('./data.txt',(err, data)=>{
    if(err){ 
       console.log(err)
       reject(err)
    }else {
       resolve(data)
    }
  })
}
複製代碼
  • 咱們如今用node異步讀取一個文件
  • 成功執行 resolve(data),失敗執行 reject(err)

如今咱們定義出這個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)
      })
    })
  }    
}
複製代碼
  • 其實很簡單,就是咱們就是在executor多傳遞進去一個reject
  • 根據異步執行的結果去判斷執行resolve,仍是reject
  • 而後咱們在MyPromise爲reject定義出和resolve一樣的方法
  • 而後咱們在then的時候應該傳進去兩個參數,fn,fn2

本節代碼:promise3.js

這時候將executor函數封裝到asyncReadFile異步讀取文件的函數

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);
複製代碼

這就是咱們平時封裝異步Promise函數的過程。但這是過程有沒有以爲在哪見過。若是以前executor中的'./data.txt'咱們是經過參數傳進去的那麼這個過程不就是上一節咱們提到的柯里化。

本節代碼:promise4.js

咱們再來總結一下上面的過程。

  • 咱們先進行了初始化,去執行傳進來的 executor函數,並把處理的函數push進入callback數組中
  • 在reslove或reject執行時,咱們去執行callback中的函數

  • 咱們能夠看到一樣一個函子a在不一樣時期有着不同的狀態。
  • 顯然若是在reslove()或者 reject( )以後咱們再添加then()方法是不會有做用的

那麼咱們如何解決reslove以後a函子的then調用問題呢,其實reslove以後,咱們已經有了value值,那不就是咱們最開始講的普通函子的鏈式調用嗎?因此如今咱們只須要標記出,函子此時的狀態,再決定如何調用then就好啦

4 增長Promise狀態

  • 咱們定義進行中的狀態爲pending
  • 已成功執行後爲fulfilled
  • 失敗爲rejected
class MyPromise {
  constructor (executor) {
    let _this = this;
    this.status = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    function resolve(value){
      if (_this.status === 'pending') {
        _this.status = 'fulfilled';
        _this.value = value;
        _this.onResolvedCallbacks.forEach(item => item())
      }
    }
    function reject(reason){
      if (_this.status === 'pending') {
        _this.status = 'rejected';  
        _this.reason = reason;
        _this.onRejectedCallbacks.forEach(item => item());
      }
    }
    executor(resolve, reject);
  } 
  then (fn,fn2) {
     return new MyPromise((resolve,reject) => {
      if(this.status === 'pending'){
        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)
        })
      }
      if(this.status === 'fulfilled'){
          let x = fn(this.value)
          resolve(x)
      }
      if(this.status === 'rejected'){
          let x = fn2(this.value)
          reject(x)
      }
    })
  }    
}

var a = asyncReadFile('./data.txt');
a.then(add,mismanage).then(add,mismanage).then(add,mismanage);
複製代碼

咱們分析一下上面這個過程

其實就多了一個參數,而後判斷了一下,很簡單。那麼咱們如今來分析一下,當咱們調用fulfilled狀態下的a的執行過程

setTimeout(()=>{ d = a.then(add);} ,2000)
value:"1"
複製代碼

  • (1)先執行new MyPromise(),初始化d
  • (2)而後執行 executor(resolve, reject);fn開始執行,算出新的值x
  • (3)傳給d的resolve執行,
  • (4)修改stauts和value的狀態
  • (5)return 出新的函子,能夠繼續鏈式調用

咱們來想一個問題,若是(2)中fn是一個異步操做,d後邊繼續調用then方法,此刻pending狀態就不會改變,直到resolve執行。那麼then的方法就會加到callback上。就又回到咱們以前處理異步的狀態啦。因此這就是爲何Promise可以解決回調地獄

參考代碼:promise5.js

好了,咱們如今來看傳進去的方法fn(this.value) ,咱們須要用上篇講的Maybe函子去過濾一下。

5 Maybe函子優化

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)
      }
    })
  }    
複製代碼
  • Maybe函子很簡單,對onResolved和onRejected進行一下過濾

參考代碼:promise6.js

這一篇先寫到這裏吧。最後總結一下,Promise的功能很強大,就是少年派的奇幻漂流同樣。雖然旅程絢爛多彩,但始終陪伴你的只有那隻老虎。Promise也是同樣,只要掌握其核心函子的概念,其餘問題就比較好理解啦。這裏只實現了一個簡單的Promise,更強大的功能,咱們慢慢加吧。

相關文章
相關標籤/搜索