函数式编程之Promise的奇幻漂流

2018-07-09  本文已影响57人  流动码文

函数式编程之Promise的奇幻漂流

上一篇我们讲了同步链式处理数据函子的概念。这一节,我们来讲异步。用到的概念很简单,不需要有函数式编程的基础。当然如果你看了那篇 《在你身边你左右 --函数式编程别烦恼》 会更容易理解。这一篇我们会完成一个Promise代码的编写。本文会从实现一个只有十几行代码能够解决异步链式调用问题的简单的Promise开始。然后逐渐完善增加功能。

本文代码在我的github

1 实现简单的Promise函子

我们先来回顾一下同步链式调用。

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

复制代码
image.png

那么如果当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);

复制代码

这样我们就轻松的完成了a这个对象的赋值。那么我们怎么用方法去处理这个数据呢?

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)
复制代码

现在我们已经实现了异步的链式调用,我们来具体分析一下,都发生了什么。

image.png

注意:这时map中this指向的是a函子,但是 new Functor((resolve) => {}中resolve是B的

我们再来分析一下异步结束之后,回调函数中的resolve是如何执行的。

image.png

本节代码: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);

复制代码

3 增加reject回调函数

我们都知道,在异步调用的时候,我们往往不能拿到数据,返回一个错误的信息。这一小节,我们对错误进行处理。

function executor(resolve,reject){
  fs.readFile('./data.txt',(err, data)=>{
    if(err){ 
       console.log(err)
       reject(err)
    }else {
       resolve(data)
    }
  })
}
复制代码
image.png

现在我们定义出这个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)
      })
    })
  }    
}
复制代码

本节代码: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

我们再来总结一下上面的过程。

image.png

那么我们如何解决reslove之后a函子的then调用问题呢,其实reslove之后,我们已经有了value值,那不就是我们最开始讲的普通函子的链式调用吗?所以现在我们只需要标记出,函子此时的状态,再决定如何调用then就好啦

4 增加Promise状态

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);
复制代码

我们分析一下上面这个过程

image.png

其实就多了一个参数,然后判断了一下,很简单。那么我们现在来分析一下,当我们调用fulfilled状态下的a的执行过程

setTimeout(()=>{ d = a.then(add);} ,2000)
value:"1"
image.png

我们来想一个问题,如果(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)
      }
    })
  }    
复制代码

原文地址:https://juejin.im/post/5b41c159e51d4519277b6a39?utm_source=gold_browser_extension

上一篇 下一篇

猜你喜欢

热点阅读