zoukankan      html  css  js  c++  java
  • Promise的原理探究及手写Promise

     前言:

    不知道大家有没有一个烦恼,就是东西太多,学习完成后就会忘记了许多,在看Promise的时候,看到了Promise的各种规范,如Promise/APromise/BPromise/D 以及 Promise/A 的升级版 Promise/A+,而es6使用了Promise/A+规范

    目录结构

    参考文献

    进入正题

    前言

    promise 在应用开发和使用中非常广泛,本次主要是为了深入了解promise的工作原理,及使用流程进行手写自己的promise实现;
    其中包含了promise的使用用法和原理实现,

    Promise介绍

    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
    所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

    特点:

    1. 对象不受外界影响,共包含三种状态pengding(进行中,初始状态),fulfilled(已成功)、reject(已失败);只有异步操作结果,才能够改变当前的状态,其他的手段无法改变
    2. 一旦状态改变,就不会在变,其中只包含两种可能的状态变化 pending->fulfilled和pending变为reject,只要有这两种情况发生,状态就会凝固,不会在发生改变了

    缺点:

    1. 无法取消promise,一旦新建就会立刻执行无法中途取消,
    2. 如果不设置回调函数,内部发生错误不会反应到外部;
    3. 处于pending的状态时候,无法得到目前是哪一个阶段

    Promise进入正题(手写实现)

    本次手写也是参照此用法,对输入和输出进行的控制
    Promise 作为一个构造函数,其在new的时候就是立刻进行执行,根据其属性和行为去构建基础执行流程

    基础框架搭建

    promise/A+规范

    此处重点梳理
    Promies/A+规范要求:
    • 当promise的状态是pending的时候,可能会转化到 fulfilled或者rejected状态
    • 当promise状态是filfilled的时候
      • 不能转化成其他的状态
      • 必须返回一个value,并且这个value保持不变
    • 当promise的状态是reject的时候
      • 也无法转变成其他的状态
      • 必须返回一个失败的原因,

    定义promise的状态常量

    const PENDING = 'pending';
    const RESOLVED = 'fulfilled'; //成功
    const REJECTED = 'rejected' //失败

    创建promise基础框架

    //创建Promise的基本类
    class Promise {
      //看这个属性 能够在原型上使用 看属性是否公用
      constructor(executor) {
        this.status = PENDING;
        //成功的值
        this.value = undefined;
        //失败的原因
        this.reason = undefined;
     
        //回调函数存储器 主要解决异步处理流程
        this.onReslovedCb = []; //成功回调
        this.onRejectedCb = []; //失败回调
    
        //成功函数
        let resolve = (value) => {
        //只有在pending的时候才可以调用
          if (this.status == PENDING) {
            this.value = value;
            this.status = RESOLVED;
            this.onReslovedCb.forEach(fn => fn())
          }
        }
        //失败函数
        let reject = (reason) => {
            //只有在pending的时候才可以调用
          if (this.status == PENDING) {
            this.reason = reason;
            this.status = REJECTED
            this.onRejectedCb.forEach(fn => fn())
          }
        }
        try {
          //执行器 默认会立即执行
          executor && executor(resolve, reject);
        } catch (e) {
          //执行的时候出现错误
          reject(e)
        }
      }
    }

    Promise.prototype.then

    promise.then是用来接收promise实例的执行结果,then法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
    先看下promise/A+的规范 粗略列举了重要的内容
    promise.then(onFulfilled, onRejected)
    • onFulfilled,onRejected是then两个参数
      • 如果onFulfilled 不是函数,将会被直接忽略
      • 同理 onRejected不是函数,也会被直接忽略
    • onFulfilled 是函数
      • 当promise的状态是成功状态的时候,其将会被回调,返回的value会是第一个参数
      • 不能被进行调用在其他的状态,而且只能调用一次
    • onRejected是函数的时候
      • 当promise状态是失败时候被调用,失败的原因是第一个参数
      • 不能在其他的状态下被调用,只能被调用一次
    • then方法能够在同一个peomise上能够被调用多毮次
      • 如果当前promise的状态是fulfilled/onRejected,所有的then回调都必须按照他们的调用初始顺序执行
    • then方法 必须返回一个promise
    实现代码:根据promise/A+规范
    then(onfulfilled, onrejected) {
        //参数是可选则的参数 需要进行判断是否存在
        onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : data => data
        onrejected = typeof onrejected == 'function' ? onrejected : error => {
          throw error
        }
        //里面的函数会立刻执行
        let promise2 = new Promise((resolve, reject) => {
          //成功的时候
          if (this.status == RESOLVED) { 
            //定时器处理异常 为了保障promise2已经用完了
             setTimeout(() => {
              //try 执行函数的时候会报错 在then里面的数据
              try {
                //x 需要判断是否是promise和规整化  
                let x = onfulfilled(this.value)
                resolvePromise(promise2, x, resolve, reject)
              } catch (e) {
                reject(e)
              }
            }, 0)
          }
          //失败的时候
          if (this.status == REJECTED) {
            setTimeout(() => {
              try {
                let x = onrejected && onrejected(this.reason)
                resolvePromise(promise2, x, resolve, reject)
              } catch (e) {
                reject(e)
              }
            }, 0)
          }
          //如果当前是pending 表示还没返回回来
          if (this.status == PENDING) {
            //如果是异步 先订阅好
            this.onReslovedCb.push(() => {
              //todo... 
              setTimeout(() => {
                try {
                  let x = onfulfilled(this.value)
                  resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                  reject(e)
                }
              }, 0)
            })
            this.onRejectedCb.push(() => {
              //todo...
    
              setTimeout(() => {
                try {
                  let x = onrejected(this.reason)
                  resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                  reject(e)
                }
    
              }, 0)
            })
          }
    
        })
        return promise2;
      }
    then方法返回一个promise的函数,注意在进行promise2的创建的时候,我们在进行处理时候可能获取的到的是underfined的promise2,因此需要开辟宏任务,promise2创建完成的时候在进行调用,而在进行处理的时候,我们在onfulfilled、和onrejected得到的参数可能不同,他们收到的参数可能为几种,
    .then(data=>{
        return value;
    },err=>{
        return value
    })

    value为使用者输入,可能存在的值也是不确定的,因此需要进行判断,而onfulfilled、onRejected调用后的结果也是不确定的,因此需要进行类型的判断

    //判断then里面的函数返回值来进行判断  x表示当前onreject
    //promise都遵循的规范,因此需要进行兼容写法
    function resolvePromise(promise2, x, resolve, reject) {
      //判断当前的x是不是promise  是不是同一个 如果是同一个 就不要等待来了 
      if (promise2 === x) {
        return reject(new TypeError("调用存在错误"))
      }
      //如果x是对象或者函数 判断数据类型 
      /**
       * typeof 基本类型
       * constructor 
       * instanceof 判断实例
       * Object.toString
       */
      if (typeof x === 'object' && typeof x !== null || typeof x == 'function') {
        let called; //内部测试的时候,会成功和失败都调用一下 
        try {
          //取返回结果 then有可能通过defineProperty定义的
          let then = x.then
          //当前存在then方法 姑且是Promise
          if (typeof then === 'function') {
            //绑定this 到返回的x上,保证不用再次取then的值
            then.call(x, y => {
              if (called) return;
              called = true; //防止多次调用成功和失败
              //y可能还是promisee  //采用promise的成功结果向下传递
              resolvePromise(promise2, y, resolve, reject)
            }, r => {
              if (called) return;
              called = true;
              reject(r) //采用失败结果乡下传递
            }) //保证再次取到then的值
          } else {
            //说明x就是一个普通的对象 直接成功即可
            resolve(x)
          }
        } catch (e) {
          //promise 失败 还能进行调用成功
          //是一个普通的值 直接让promise2成功即可
          if (called) return;
          called = true;
          reject(e)
        }
      } else {
        return resolve(x)
      }
    }
    在then方法执行完后,Promise的实例状态就会改变成resolved、或者reject,此时then方法需要兼容一异步的调用类型,因此,当进入then函数后,如果当前的promise的状态仍然是Pending,则表示当前结果还没有返回,因此需要增加onRejectedCb、onReslovedCb用来存储当前的执行函数,一旦某一个状态改变,则进行调用该存储列表中的数据,进行回调;

    Promise.prototype.finally

    finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作;
    finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
    因此可以绑定此事件在当前promise实例的then方法上,在成功的时候回调传入的函数,在失败的时候也进行回调传入的参数
    /**
     *  finally 函数 promise m每次执行后都会进行执行
     * @param {*} cb 
     */
    Promise.prototype.finally = function (cb) {
      //finally 传入函数,无论成功或者失败都会执行 
      return this.then(data => {
        //Promise.resolve 可以等待这个promise完成
        return Promise.resolve(cb().then(() => data))
      }, err => {
          //失败的时候也执行
        return Promise.reject(cb().then(() => {
          throw err
        }))
      })
    }

    Promise.prototype.catch

    Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
    //异常处理 用于指定发生错误时的回调函数。
    //promise抛出一个错误,就被catch()方法指定的回调函数捕获
    Promise.prototype.catch = function (onRejected) {
      return this.then(undefined, onRejected)
    }

    Promise.all

    Promise.all可用于接收一个数组作为参数,参数可以不是数组,但是必须有Iterator接口,且返回的每个成员都是Promise的实例,他的结果是根据传入的数据进行变化的
    const p = Promise.all([p1, p2, p3]);
    • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
    • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
    /**
     * 全部成功才能成功,一个失败才会失败
     * promiseList 表示当前传递的数组对象
     */
    Promise.all = function (promiseList) {
      return new Promise((resolve, reject) => {
        let arr = [];
        let index = 0;
        //解决多个异步并发的问题
        function proceessData(key, value) {
          arr[key] = value;
          if (++index == promiseList.length) {
            resolve(arr)
          }
        }
        for (let i = 0; i < promiseList.length; i++) {
          let current = promiseList[i];
          if (isPromise(current)) {
            current.then((data) => {
              proceessData(i, data)
            }, (err) => {
              console.log("data")
              reject(err)
            })
          } else {
            proceessData(i, current)
          }
        }
      })
    }
    
    function isPromise(value) {
      if ((typeof value === 'object' && value !== null) || typeof value === 'function') {
        if (typeof value.then == 'function') {
          return true
        }
      }
      return false;
    }

    Promise.race

    Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
    const p = Promise.race([p1, p2, p3]);
    上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
    /**
     * 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
     *  @param {array} promiseList 传递的参数列表对象
     */
    Promise.race = function (promiseList) {
      // console.log(promiseList)
      //将values中的内容包装成promise的 
      if (!Array.isArray(promiseList)) {
        return Promise.resolve();
      }
      promiseList = promiseList.map(item => {
        return !isPromise(item) ? Promise.resolve(item) : item;
      });
      // 有一个实例率先改变状态则进行操作   
      return new Promise((resolve, reject) => {
        promiseList.forEach((pro, index) => {
          pro.then(res => {
            resolve(res)
          }, err => {
            reject(err)
          })
        })
      })
    }
     

    Promise.allSettled

    Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。
    /**
     * 方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,
     * 不管是fulfilled还是rejected,包装实例才会结束
     */
    Promise.allSettled = function (promiseList) {
      return new Promise((resolve, reject) => {
        let index = 0;
        let arr = [] ;
        //用于记录当前的promise的执行状态
        function recordRequest(key, value) {
          index++;
          arr[key] = value;
          //选择这种计数的方式,主要是考虑存在异步的流程,等待所有流程都执行完成后在结束
          if (index == promiseList.length) {
            resolve(arr)
          }
        }
        for (let i = 0; i < promiseList.length; i++) {
          current = promiseList[i]
          if (isPromise(current)) {
            current.then((data) => {
            //每执行完成一个,就去增加记录
              recordRequest(i, {
                status: 'resolve',
                value: data
              })
            }, (err) => {
               //失败的promise也记录
              recordRequest(i, {
                status: 'reject',
                reason: err
              })
            })
          } else {
            recordRequest(i, {
              status: '',
              value: current
            })
          }
        }
      })
      }

     Promise.any

    Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
    /**
     * Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案 。
     * @param {*} promiseList promise的参数列表
     */
    Promise.any = function(promiseList){
      promiseList = promiseList.map(item => {
        return !isPromise(item) ? Promise.resolve(item) : item;
      });
      let index = 0; 
      let result=[]
      return new Promise((resolve,reject)=>{
        for (let i = 0; i < promiseList.length; i++) {
          current = promiseList[i]
          if (isPromise(current)) {
            current.then((data) => {
              resolve(data)
            }, (err) => { 
              index++; 
              result.push(err)
              if(index == promiseList.length){
                reject(err);
              } 
            })
          } 
        }
      })
    }
    Promise.any()抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。下面是 AggregateError 的实现示例。

    Promise.resolve

    有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。会返回一个状态为Resolved状态的promise
    Promise.resolve(value),其中value的值包含好多种
    • 参数是一个 Promise 实例
    • 参数是一个thenable对象
    • 参数不是具有then()方法的对象,或根本就不是对象
    • 不带有任何参数
    参数的类型可能存在几种情况
    /**
     * Promis.resolve 函数
     * @param {*} values 传递进来的变量函数
     */
    Promise.resolve = function (values) {
      //1.参数是一个 Promise 实例 将原封不动的返回
      if (values instanceof Promise) {
        return values;
      }
      return new Promise((resolve, reject) => {
        //2.参数是一个含有then对象 具有then方法
        //Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。
        if (isPromise(values)) {
          values.then(resolve, reject);
        } else {
          //3.参数不是具有then()方法的对象,或根本就不是对象  如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。
          //4.参数不是具有then()方法的对象,或根本就不是对象
          //5.不带有任何参数 
          resolve(values)
        }
      })
    }

     Promise.reject

    Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
    /**
     * //Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
     * 参数为values字符串
     */
    Promise.reject = function (values) {
      return new Promise((resolve, reject) => {
          reject(values)
      })
    }

    Promise.try

    实用场景:
    不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。
    由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,用Promise.try包装一下,可以更好地管理异常。
    Promise.try = function (fn, argumnts = null, ...args) {
      if (typeof fn == 'function') {
      //立刻执行fn函数并进行调用返回
        return new Promise(resolve => resolve(fn.apply(argumnts, args)))
      } else {
        const err = new TypeError(`${typeof fn} ${fn} is not a function`);
        return Promise.try(() => {
          throw err
        });
      }
    }

    总结

    Promise的使用方法在我们使用中还是比较多,在进行使用的时候,要进行判断需要使用哪一个方法,掌握Promise的类方法和原型方法,其中原型方法可以进行由于返回的是promise 因此可以进行链式的调用,而直接调用类方法后,也可以进行链式的调用
    关于几个传入多个函数的Promise的方法进行总结表
    方法 作用
    Promise.all()

    1.所有的状态都变成功状态,才会返回成功,此时的结果将会组成一个数组,进行返回

    2.其中一个为rejected的时候,p的状态就会变成rejected,返回第一个被rejected的实例返回值

    Promise.any()

    1.接收一组promise的实例,

    2.只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;

    3.如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态

    Promise.race() 1.只要其中的一个实例先改变状态,p的状态就会改变,哪个率先改变Promise的返回值,就会传给实例函数
    Promise.allSettled()

    1.接收多个请求

    2.等待所有的函数都执行完成后才会进行返回

    3.不涉及到函数返回的状态是成功还是失败,只要都处理完成,才返回

     
     
     
     
     

  • 相关阅读:
    在winform下实现左右布局多窗口界面的方法(一)
    C# 使用API检查域用户名和密码是否正确
    C#检查网络是否可以连接互联网
    总结:实体类和(XML或二进制)之间相互转(序列化和反序列化)
    XML和实体类之间相互转换(序列化和反序列化)
    C# XML反序列化与序列化举例:XmlSerializer
    XML文件与实体类的互相转换
    Message类的属性Msg所关联的消息ID
    C# Message 消息处理
    在.net中读写config文件的各种方法(自定义config节点)
  • 原文地址:https://www.cnblogs.com/mfyngu/p/13880867.html
Copyright © 2011-2022 走看看