Promise期约函数的实现

news2024/10/3 4:37:09

前言

Promise也叫期约函数,是ES6中新增的特性,是解决异步编程的一种方案,取代回调函数避免回调地狱。

const p = new Promise((resolve,reject)=>{
  resolve(1);
});

// 链式调用
p.then(res => Promise.resolve(res + 2))
 .then(res => Promise.resolve(res + 3))
 .then(res => console.log(res));
 // 6

Promise的一些特性和约定:

  • Promise是一个构造函数,接受一个executor函数,executor在new的时候会立即执行,executor接收resolve和reject两个函数作为参数。
  • 期约函数有三个状态,pending、fullfiled、reject,初始为pending状态,只能通过resolve和reject改变期约的状态,并且这个过程不可逆。

Promse的实现思路
Promise的核心就是通过resolve/reject函数获取到value/reason,通过then函数获得回调函数,然后将value传入回调函数执行,通过then函数保证执行顺序。具体过程如下:

  1. 通过resolve/reject获取到value/reason
  2. 通过then函数获取callback
  3. 通过then函数保证callback的执行顺序

Promise本质上就是通过then函数将callback串联执行,这点和compose有点类似。

1. Promise构造函数

  • 只能通过resolve/reject改变期约函数的状态,状态改变不可逆
  • executor函数立即执行
    function MyPromise(executor) {
      this.value = undefined;
      this.reason = undefined;
      this.state = 'pending';
      this.onResolveCllbacks = [];
      this.onRejectCllbacks = [];

      const resolve = (val) => {
        if (this.state === 'pending') {
          this.value = val;
          this.state = 'fullfilled';
          // 执行保存的回调
          if (this.onResolveCllbacks.length > 0) {
            this.onResolveCllbacks.forEach(fn => fn(this.value));
            this.onResolveCllbacks = [];
          };
        }
      }

      const reject = (reason) => {
        if (this.state === 'pending') {
          this.reason = reason;
          this.state = 'reject';
          // 执行保存的回调
          if (this.onRejectCllbacks.length > 0) {
            this.onRejectCllbacks.forEach(fn => fn(this.reason));
            this.onRejectCllbacks = [];
          };
        }
      }
      executor(resolve, reject);
    };

2. Array.prototy.then函数实现

then函数接收onResolve和onReject两个参数作为回调函数,并返回一个新的Promise,返回Promise的规则如下:

  • 没有传递onResolve/onReject或者它们没有返回值的时候,直接返回原来的Pomise函数
  • onResolve/onReject的返回值是个Promsie的时候直接返回这个Promise
  • onResolve/onReject返回值为其它类型的时候,相当于返回Promise.resolve(onResolve())或Promise.reject(onReject())
    MyPromise.prototype.then = function (onResolve, onReject) {
      if (this.state === 'fullfilled') {
        if (typeof onResolve === 'function') {
          const result = onResolve?.(this.value);
          return result instanceof MyPromise ? result : result ? new MyPromise((resolve, reject) => resolve(result)) : this;
        } else {
          return this;
        };
      };
      if (this.state === 'reject') {
        if (typeof onReject === 'function') {
          const result = onReject?.(this.reason);
          return result instanceof MyPromise ? result : result ? new MyPromise((resolve, reject) => reject(result)) : this;
        } else {
          return this;
        }
      };

      // 如果此时还没有通过resolve得到value就存到数组中,等到resolve的时候执行
      if (this.state === 'pending') {
        return new MyPromise((resolve, reject) => {
          typeof onResolve === 'function' && this.onResolveCllbacks.push((value) => {
            const result = onResolve(value);
            if (result instanceof MyPromise) {
              result.then(res => resolve(res), reason => reject(reason));
            } else {
              resolve(result);
            };
          });
          typeof onReject === 'function' && this.onRejectCllbacks.push((reason) => {
            const result = onReject(reason);
            if (result instanceof MyPromise) {
              result.then(res => resolve(res), reason => reject(reason));
            } else {
              resolve(result);
            };
          });
        });

then函数的逻辑要考虑两种情况,一种是resolve函数先于then函数执行,这种情况发生在excutor的resolve语句为同步代码:

   const p  = new MyPromise((resolve,reject)=>resolve('result'));

当executor的resolve包含异步操作时,then函数是先于resolve执行的

    const p  = new MyPromise((resolve,reject)=>{
      setTimeout(()=>{
        resolve('result');
      },200)
    });
  1. 针对第一种情况直接then函数中直接能获取到resolve/reject的value/reason,然后将其传入onResolve/onReject执行即可。对于第二种情况,要先将onResolve/onReject保存起来,等到resolve/reject获取到value/reason的时候再执行。

  2. 第二种情况下对于then函数返回的Promsie也要特殊处理,then函数返回Promise是依赖于父级Promise的state和value/reason的,具体的处理方法是直接返回一个新的Promise,把它的resolve和reject方法通过闭包保存下来,等到resolve的时候执行。

onResolveCllbacks和onRejectCllbacks之所以是个数组是因为then函数可以被多次调用,会产生多个回调函数

   var p = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve(1);
      }, 2000);
    });
    p.then(res=>console.log('第一次调用',res));
    p.then(res=>console.log('第二次调用',res));
    p.then(res=>console.log('第三次调用',res));

3. 基础使用

3.1 链式调用

    const p = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve(1);
      }, 100);
    });

    function createPromise(initialValue, value) {
      return new MyPromise((resolve, reject) => {
        setTimeout(() => {
          resolve(initialValue + value)
        }, 200);
      })
    };
    
    p.then(res=>createPromise(res,2))
    .then(res=>createPromise(res,3))
    .then(res=>console.log(res)); 
    // 6

3.2 执行同步和异步代码

    // 同步操作
    var p1 = new MyPromise((resolve, reject) => {
        resolve(1);
    });
    p1.then(res=>console.log(res)); //1
    
    // 异步操作
    var p2 = new MyPromise((resolve, reject) => {
       setTimeout(()=> resolve(1);,100)
    });
    p2.then(res=>console.log(res)); //1

3.3 多次调用

    var p = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve(1);
      }, 100);
    });

    p.then(res=>console.log('第一次调用',res)); //第一次调用 1
    p.then(res=>console.log('第二次调用',res)); //第二次调用 1
    p.then(res=>console.log('第三次调用',res)); //第三次调用 1

4. Array.prototy.catch函数实现

catch函数是onReject的语法糖,onReject与catch同时存在时只会执行onReject。catch函数也返回一个Promise,只不过这个场景很少用,下面的代码中不包含返回Promsie的部分。

    MyPromise.prototype.catch = function (onReject) {
      if (this.state === 'reject') {
        onReject?.(this.reason)
      } else {
        typeof onReject === 'function' && this.onRejectCllbacks.push(onReject);
      };
    };

5. Promis.resolve、Promsie.reject的实现

    MyPromise.resolve = function (value) {
      return new MyPromise((resolve, reject) => resolve(value))
    };

    MyPromise.reject = function (reason) {
      return new MyPromise((resolve, reject) => reject(reason));
    };

    //使用
    const p1 = MyPromise.resolve('result');
    p1.then(res => console.log(res)); //result

    const p2 = MyPromise.reject('reason');
    p2.then(null, reason => console.log(reason)); //reason

6. Promise.all的实现

Promise.all用来执行多个Promise,返回一个新的Promise,返回的这个promise规则如下:

  • 所有的promise都成功,Promsie.all才算成功,将它们的结果放到数组中作为新的promise.all的结果
  • 有一个promsie失败,Promsie.all就算失败,将第一个出现错误的Promise的信息作为reject的信息

Promise起到的作用是按顺序执行Promise,将它们的结果序列化。

    MyPromise.all = function (fns) {
        const res = [];
        let count = 0;
        return new MyPromise((resolve, reject) => {
          function next() {
            fns[count].then(res => {
              if (count === fns.length) {
                resolve(res);
              } else {
                count++
                res.push(res);
              };
            }, reason => {
                reject(reason);
            });
          };
          next();
        });
      }; 
      
 //使用
 var a = new MyPromise((resolve,reject)=>setTimeout(() => {
    resolve(1)
  }, 200));
  var b = new MyPromise((resolve,reject)=>setTimeout(() => {
    resolve(2);
  }, 100));

  var c = new MyPromise((resolve,reject)=>setTimeout(() => {
    resolve(3)
  }, 300));

  var p = Promise.all([a,b,c]);
  p.then(res=>console.log(res)); //[1,2,3]

7. Promise.race的实现

race函数接收一个promsie数组进行赛马,返回一个新的promsie,并且这个promsie的结果依赖于先跑完的promise。

    MyPromise.race = function (promiseFns) {
      return new MyPromise((resolve, reject) => {
        promiseFns.forEach((fn) => {
          fn.then(res => resolve(res), reason => reject(reason));
        });
      })
    };
    
  // 使用
  var a = new MyPromise((resolve,reject)=>setTimeout(() => {
    resolve(1)
  }, 500));
  var b = new MyPromise((resolve,reject)=>setTimeout(() => {
    resolve(2);
  }, 800));

  var c = new MyPromise((resolve,reject)=>setTimeout(() => {
    resolve(3)
  }, 300));

  var p = Promise.race([a,b,c]);
  p.then(res=>console.log(res),reason=>console.log(reason)); //3

8. promsie的串行

使用compose串行promsie

    function add1(x) {
      return x + 1;
    };
    function add3(x) {
      return x + 3;
    };
    function add5(x) {
      return x + 5;
    };

    const fns = [add1,add3,add5];
    const add9 = fns.reduce((promise,cb)=>promise.then(cb),Promise.resolve(1));
    add9.then(res=>console.log(res)); //10

    //进一步封装
    const add = fns => x => fns.reduce((promsie, cb) => promsie.then(cb), Promise.resolve(x));
    const add9 = add([add1, add3, add5]);
    add9(10).then(res => console.log(res)); //19

9. Promise的不足

  • pending状态无法判断是刚开始还是已经结束,状态的变化没有通知
  • 一旦创建无法取消
  var a = new Promise(()=>{}).then();
  console.log(a);

  var b = Promise.resolve().then(res=>new Promise(()=>{}));
  console.log(b);

在这里插入图片描述
promsie无法取消的问题可以通过添加方法手动实现,或者使用一些像cancelPromise这样的三方库,下面的代码利用Promsie.race简单实现了对promsie的中断

    function cancelPromsie(promise) {
        Promise.race([promise, new Promise((resolve, reject) => { resolve('cancel') })]);
      };

      var p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log('p1')
          resolve('p1');
        }, 1000);
      });

      cancelPromsie(p1);

Promise的取消本意上是停止掉不需要的异步函数,取消promsie本身没有太大的意义,es6之所以没有支持取消promsie是因为取消promise会使得then函数的链式调用和Promsie.all的执行逻辑更加复杂。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/54169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式系统硬件概述

文章目录嵌入式系统硬件平台(1) 嵌入式处理器的分类(2) 嵌入式微处理器MIPS处理器PowerPC处理器ARM处理器ARM发展历史ARM公司介绍ARM市场份额嵌入式微控制器(MCU)数字信号处理器(DSP)嵌入式片上系统(SoC)嵌…

【uni-app】总结uni-app订单支付和打包发布

前言 总结uni-app订单支付和打包发布 1- 支付 1.1 app的支付 1.1.1 准备工作 支付厂商 获取id 去微信支付平台接入微信支付 支付宝 打包时候 去mainifest.json文件下,找到 app模块配置 ,勾选payment支付 1.1.2 代码 获取支付厂商 uni.getProvide() u…

Spring Boot 程序优化的 14 个小妙招!

1.定义配置文件信息 有时候我们为了统一管理会把一些变量放到yml配置文件中 例如 图片 用 ConfigurationProperties 代替Value 使用方法 定义对应字段的实体 Data // 指定前缀 ConfigurationProperties(prefix "developer") Component public class Developer…

数据看板是什么?

一 数据看板定义 数据看板是数据可视化的载体。数据看板是一个可视化工具,通过合理的页面布局、效果设计,将可视化数据更直观、更形象的展现出来;数据看板是一个交流工具,通过数据公开和呈现,公司内部能够共享有效信息…

大二学生基于Html+Css+javascript的网页制作——动漫设计公司响应式网站模板 (10个页面)

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置,有div的样式格局,这个实例比较全面,有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 ⚽精彩专栏推荐&#x1…

JAVAWEB_实验二 JSP 的内置对象

文章目录一、Part 1 JSP 的内置对象一、实验目的二、实验要求三、实验内容二、Part 2 JSP 的内置对象一、实验目的二、实验要求三、实验内容思考:页面重定向有哪些方式?区别时什么?一、Part 1 JSP 的内置对象 一、实验目的 通过编程和上机实…

算法刷题打卡第33天:香槟塔

香槟塔 难度:中等 我们把玻璃杯摆成金字塔的形状,其中第一层有 1 个玻璃杯,第二层有 2 个,依次类推到第 100 层,每个玻璃杯 (250ml) 将盛有香槟。 从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满…

【JavaWeb】Servlet系列 --- 使用纯Servlet做一个单表的CRUD操作(oa小项目,超详细笔记)

使用纯Servlet做一个单表的CRUD操作实现步骤第一步:准备一张数据库表(sql脚本/可视化工具)第二步:准备一套HTML页面(页面原型)【前端开发工具使用vscode / IDEA】第三步:分析我们这个系统包括哪…

分布式共识协议 Raft 是如何工作的?

Raft 解决的问题 提供一种共识算法(分布式一致性算法)。 Paxos是早先的一个分布式共识算法,Paxos 逻辑复杂而难以理解和实现。相比早先的 Paxos, Raft 提供一个容易理解和实现的共识算法,在很多的系统比如 etcd, ozon…

力扣hot100——第3天:11盛最多水的容器、21合并两个有序链表、22括号生成

文章目录1.11盛最多水的容器1.1.题目1.2.解答1.2.1.题解1.2.2.自己对参考题解的进一步解释2.21合并两个有序链表2.1.题目2.2.题解3.22括号生成3.1.题目3.2.题解1.11盛最多水的容器 参考:力扣题目链接;题解 1.1.题目 1.2.解答 1.2.1.题解 这道题目可以…

GIS工具maptalks开发手册(一)——hello world初始化

GIS工具maptalks开发手册(一)——hello world初始化 为何使用maptalks? ​ Maptalks项目是一个HTML5的地图引擎, 基于原生ES6、Javascript开发的二三维一体化地图。 通过二维地图的旋转、倾斜增加三维视角,通过插件化设计, 能与其他图形库echarts、d3.…

微信小程序实战十四:小程序及APP端实现客服功能

文章目录 1.效果预览2.小程序后台添加客服3.小程序代码中集成客服4.APP中添加客服5.企业微信登陆6.获取企业ID值7.设置多客服说明:项目用uni开发的,有小程序版本和APP版本,最开始项目中集成了第三方美洽的客服,2个客服一年收3600,老哥咨询我是否有稍微优惠点的方案,老哥带…

QuEra将研发可重构中性原子量子计算机

(图片来源:网络) 上个月,借助Amazon Braket,QuEra Computing开始提供对其中性原子量子系统Aquila的访问, Aquila具有256个量子比特。如今,量子公司的数量与日俱增,QuEra是其中之一,它…

java httpclient的digest验证(可恨,找遍全网没有靠谱的,不是少包就是少文件。含泪整理o(╥﹏╥)o~~~~)

背景:调用第三方接口,使用的是digest auth鉴权方式, basic auth和digest auth比较: basic认证是把用户和密码通过base64加密后发送给服务器进行验证。 Basic认证过程简单,每次请求都有发送密码。安全性较低。 为了解决…

[附源码]JAVA毕业设计衡师社团管理系统(系统+LW)

[附源码]JAVA毕业设计衡师社团管理系统(系统LW) 目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术…

【allegro 17.4软件操作保姆级教程八】布线操作基础之三

目录 1.1扇出操作 1.2差分对过孔间距调整 1.3差分线换层自动添加回流过孔 1.4多人协同操作 1.5导入pin delay 1.6走线导圆弧 1.1扇出操作 关于信号扇出有如下一些需要注意的点: 1、过孔扇出要考虑其间距,要求2个过孔之间保证能过一根信号线&#x…

java+jsp基于ssm的校园OTO超市系统-计算机毕业设计

项目介绍 本网站主要是针对高校学生以超市购物为重点开发的网站。系统从用户上分为三种:卖家、买家和游客。系统从模块分为买家模块和卖家模块,买家模块包括用户注册登录、商品浏览、商品详情、商品加入购物车、购物车中商品删除、购物车商品数量变更、…

vue 微信登录

文章目录前言一、第一步用户授权获取code1、PC扫码方式一方式二:踩坑记录2、移动端微信内置浏览器授权获取code二、第二步 通过code获取access_token三、获取用户个人信息前言 网站应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统。 在进行微信…

没想到吧,Spring中还有一招集合注入的写法

Spring作为项目中不可缺少的底层框架,提供的最基础的功能就是bean的管理了。bean的注入相信大家都比较熟悉了,但是有几种不太常用到的集合注入方式,可能有的同学会不太了解,今天我们就通过实例看看它的使用。 首先,声…

[附源码]JAVA毕业设计衡水特产展销系统(系统+LW)

[附源码]JAVA毕业设计衡水特产展销系统(系统LW) 目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术…