• 这一次,彻底理解Promise源码思想


    关于Promise的源码实现,网上有太多答案,我也看过很多资料,但都不是很明白。直到有一天我学完函数式编程之函子的概念,才对Promise源码有了更深刻的认识。今天,就让我们来重新认识一下Promise。

    我们知道,Promise的诞生是为了解决“回调地狱”的问题,它用同步链式的方式去解决异步的嵌套回调。

    啥?同步链式?这不就是我们上一节学习的函子的思想吗?如果对函子有所了解,那么再来学习Promise源码就比较容易理解了。接下来,我们探究一下函子和Promise有着怎样的关系。

    实现一个简单的Promise函子

    先来回顾一下函子Functor的链式调用:

    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 函数。通过 map 中传递进去的函数fn去处理函子保存的数据,用得到的值去生成新的函子。

    等等...函子是同步链式,而Promise是异步链式。也就是说上面a的值是异步产生的,那我们该何如传入 this.value 值呢?

    function executor(resolve){
      setTimeout(()=>{ resolve(100) },500)
    }
    

    我们模拟一下通过 setTimeout500 毫秒后拿到数据100。其实也很简单,我们可以传进去一个 resolve 回调函数去处理这个数据。

    class MyPromise {
       constructor (executor) {
          let self = this;
          this.value = undefined;
    
          // 回调函数,用来赋值给 value
          function resolve(value){
              self.value = value;
          }
          executor(resolve)
       } 
    }
    
    var a = new MyPromise(executor);
    

    解释一下上面的代码:我们将 executor 传入并立即执行,在 resolve 回调函数中我们能够拿到 value 值,我们定义 resolve 回调函数将 value 的值赋给 this.value。

    这样我们就轻松的完成了 a 这个对象的赋值。由于是异步得到的,那么我们怎么用方法去处理这个数据呢?

    根据函子的思想,在拿到数据之后,我们应该让 map 里传入的 fn 函数去处理数据。由于是异步处理, resolve 执行后才拿到数据,所以我们定义了一个 callback 函数,在 callback 里面执行 fn。最后把 fn 处理的结果交给下一个函子的 resolve 保存。

    class MyPromise {
       constructor (executor) {
          let self = this;
          this.value = undefined;
          this.callback = null;
          // 回调函数,用来赋值给 value
          function resolve(value){
               self.value = value
               self.callback()  // 得到 value 之后,在 callback 里面执行 map 传入的 fn 函数处理数据
          }
          executor(resolve)
       } 
      
       map (fn) {
           let self = this;
           return new MyPromise((resolve) => {
              self.callback = function(){
                  let data =  fn(self.value)   
                  resolve(data)
               }
           })
       }    
    }
    
    new MyPromise(executor).map(add1).map(add1)
    

    同时调用同一个Promise函子

    Promise除了能链式调用,还能同时调用,比如:

    var a = new MyPromise(executor);
    var b = a.map(add);
    var c = a.map(minus);
    

    像上面这个同时调用a这个函子。你会发现,它实际上只执行了c。原因也很简单,b先给a的 callback 赋值,然后c又给a的 callback 赋值。所以把b给覆盖掉了就不会执行啦。解决这个问题很简单,我们只需要让callback变成一个数组就解决了。

    class MyPromise {
       constructor (executor) {
          let self = this;
          this.value = undefined;
          this.callbacks = [];
          function resolve(value){
              self.value = value;
              self.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;

    增加reject回调函数

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

    class MyPromise {
      constructor (executor) {
        let self = this;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];
        function resolve(value){
          self.value = value;
          self.onResolvedCallbacks.forEach(item => item())
        }
        function reject(reason){
          self.reason = reason;
          self.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

    这时候将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函数的过程,这个过程有没有觉得在哪见过。仔细看下,asyncReadFile 不就是前面我们提到的柯里化。

    增加Promise状态

    我们定义进行中的状态为pending
    已成功执行后为fulfilled
    失败为rejected

    class MyPromise {
      constructor (executor) {
        let self = this;
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];
        function resolve(value){
          if (self.status === 'pending') {
            self.status = 'fulfilled';
            self.value = value;
            self.onResolvedCallbacks.forEach(item => item())
          }
        }
        function reject(reason){
          if (self.status === 'pending') {
            self.status = 'rejected';  
            self.reason = reason;
            self.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);
    

    最后,现在来看传进去的方法 fn(this.value) ,我们需要用上篇讲的Maybe函子去过滤一下。

    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进行一下过滤。

    总结

    Promise是一个很不好理解的概念,但总归核心思想还是函子。

    同时,在函子的基础上增加了一些异步的实现。异步的实现是一个比较费脑细胞的点,把加粗的字体花点时间多思考思考,加油!

    参考链接:函数式编程之Promise的奇幻漂流

    标准PromiseA+规范实现:这一次,彻底弄懂 Promise 原理

  • 相关阅读:
    kfx格式的复活
    HTC(HTML Component)开发简介
    window.open window.showModelDialog 打开一个新窗口/子窗口中调用父窗口的方法
    用 Firebug 动态调试和优化应用程序
    HTC——浏览器上的舞者
    innerHTML、innerText和outerHTML、outerText的区别
    window.parent与window.opener的区别与使用
    window.parent与window.opener、window.showModalDialog的区别 opener和showModalDialog刷新父页面的方法
    HTML Component(HTC)
    Hibernate下数据批量处理解决方案
  • 原文地址:https://www.cnblogs.com/chenwenhao/p/11775122.html
Copyright © 2020-2023  润新知