【手写 Promise 源码】第五篇 - 实现 Promise 对异步操作的支持

news/2024/11/2 1:31:32/

一,前言

上一篇,翻译并理解了整个 Promise A+ 规范;

本篇开始,将基于 Promise A+ 规范,对我们的简版 Promise 源码进行功能完善;

本篇,将实现 Promise 对异步操作的支持;


二,前文回顾

在前面几篇中,结合对 Promise 基础功能的使用介绍和简单分析,手写了一个简版 Promise 源码,主要覆盖了以下几点:

  • promise 是一个类;
  • 使用 promise 时,传入 executor 执行器,并被立即执行;
  • executor 参数是两个函数,用于描述 promise 实例的状态;
  • resolve 表示成功,可以传入一个 value;
  • reject 表示失败,可以传入一个 reason;
  • 每个 Promise 实例都有一个 then 方法;
  • promise 一旦状态发生后,不能再更改;
  • promise 有三种状态:成功态,失败态,等待态(默认);

简版源码如下:

const PENDING = 'PENDING';     // 等待态
const DULFILLED = 'DULFILLED'; // 成功态
const REJECTED = 'REJECTED';   // 失败态class Promise{// 通过 new Promise 进行实例化时,传入 executor 执行器函数constructor(executor){this.value = undefined; // 保存成功的原因,then中调用onFulfilled时传入this.reason = undefined; // 保存失败的原因,then中调用onFulfilled时传入this.state = PENDING;   // promise 状态,默认等待态// 成功 reslove 函数、失败reject函数,并传入executorconst reslove = (value) =>{// 等待态 --> 成功态if(this.state === PENDING){this.value = valuethis.state = DULFILLED;}}const reject = (reason) =>{// 等待态 --> 失败态if(this.state === PENDING){this.reason = reasonthis.state = REJECTED;}}// 立即执行 executor 执行器函数,通过 try...catch... 进行异常捕捉;try{executor(reslove, reject);}catch(e){reject(e)  // 有异常,调用 reject 更新为失败态}}// 定义 then 方法:包含两个参数 onFulfilled 和 onRejected;// 根据 Promise 状态,执行 onFulfilled 或 onRejected;then(onFulfilled, onRejected){// 成功态,调用 onFulfilled,传入成功 valueif(this.state === DULFILLED){onFulfilled(this.value)} // 失败态,执行 onRejected,传入失败 reasonif(this.state === REJECTED){onRejected(this.reason)}}
}module.exports = Promise; 

三,提出问题

前面说,Promise 的一个主要作用就是处理异步操作,使开发者使用同步编程方式处理异步操作,相比之前 callback 回调的处理方式,能够更好的解决/避免回调地狱的产生,同时还能实现控制权的反转;

1,测试原生 Promise

使用原生 Promise 执行异步操作:

let promise = new Promise((resolve, reject)=>{setTimeout(()=>{resolve('ok');}, 1000)
})promise.then((value)=>{console.log('success', value) 
},(reson)=>{console.log('err',reson)
})// success ok

2,测试手写 Promise

// 引入手写的 promise.js
let promise = new Promise((resolve, reject)=>{setTimeout(()=>{resolve('ok');}, 1000)
})promise.then((value)=>{console.log('success', value) 
},(reson)=>{console.log('err',reson)
})

执行结果将不会输出任何内容,即 then 中的成功回调 onFulfilled 和失败回调 onRejected 都没有被执行;

3,问题分析

执行过程分析:

  • 当 new Promise 完成后,executor 执行器函数被立即执行,定时器被启动;
  • promise.then 被执行,当前 promise 实例状态为 pending 态,不执行任何操作;
  • 1 秒后定义器回调执行,调用 resolve,将当前 promise 实例状态更新为成功态;

问题原因:

  • 定时器 1 秒后才会调用 reslove 函数,而此时 then 方法已经执行完毕;
  • 所以,当前 promise 源码尚不支持异步操作,需要实现对异步操作的支持;

4,解决方案

  • 使用队列先将 then 方法中的回调函数收集起来;
  • 待 resolve 或 reject 被调用状态更新后,再执行对应的回调函数即可;

这种“先收集,再执行”的功能特征,符合订阅发布模式;


四,实现 Promise 对异步操作的支持

1,事件订阅:收集回调函数

  • 当 Promise 执行异步操作时,状态为 PENDING;
  • 此时,需要两个队列(数组)对成功/失败回调分别进行收集;
class Promise{constructor(executor){this.state = PENDING;this.value = undefined;this.reason = undefined;this.onResolvedCallbacks = []; // 收集成功回调this.onRejectedCallbacks = []; // 收集失败回调// ...}then(onFulfilled, onRejected){// 执行异步操作时,收集成功/失败回调if(this.state === PENDING){this.onResolvedCallbacks.push(()=>{onFulfilled(this.value) // 传入 value})this.onRejectedCallbacks.push(()=>{onRejected(this.value) // 传入 value})}// ...}
}

这样,就完成了成功/失败回调函数的事件订阅操作;

2,事件发布:根据状态执行回调处理

当 Promise 中的 resolve 或 reject 函数被调用时,根据状态执行对应的回调处理:

class Promise{constructor(executor){this.state = PENDING;this.value = undefined;this.reason = undefined;this.onResolvedCallbacks = [];this.onRejectedCallbacks = [];const reslove = (value) =>{if(this.state === PENDING){this.value = valuethis.state = DULFILLED;// 事件发布操作this.onResolvedCallbacks.forEach(fn=>fn())}}const reject = (reason) =>{if(this.state === PENDING){this.reason = reasonthis.state = REJECTED;// 事件发布操作this.onRejectedCallbacks.forEach(fn=>fn())}}// ...}// ...
}

这里相当于“订阅发布模式”中的事件发布;

3,测试对异步操作的支持

let promise = new Promise((resolve, reject)=>{setTimeout(()=>{resolve('ok');}, 1000)
})promise.then((value)=>{console.log('success', value) 
},(reson)=>{console.log('err',reson)
})// success ok

五,结尾

本篇,主要实现了 Promise 对异步操作的支持,主要涉及以下几个点:

  • 测试 Promise 对异步操作的支持;
  • 分析当前 Promise 代码问题及解决方案;
  • 使用发布订阅思想实现对异步操作的支持;
  • Promise 异步操作的测试;

下一篇,实现 Promise 的链式调用;


http://www.ppmy.cn/news/20161.html

相关文章

嵌入式 学习

自学嵌入式当然可以,但别做单片机,单片机有基础就够了,别太深入。 先上结论,嵌入式方向太多了,学生时期最重要的是把某一方向的基础学扎实。嵌入式主要方向有Linux应用开发,Linux驱动开发,BSP&a…

函数的连续性和间断点——“高等数学”

各位CSDN的uu们你们好呀,今天小雅兰的内容是高等数学中的函数的连续性和间断点,好的,那现在就让我们进入函数的连续性和间断点的世界吧 一、函数的连续性 1.函数增量 2.连续的定义 3.单侧连续 二、例题(函数的连续性) …

Hive整合HBase,操作HBase表

Hive over HBase原理 Hive与HBase利用两者本身对外的API来实现整合,主要是靠HBaseStorageHandler进行通信,利用 HBaseStorageHandler,Hive可以获取到Hive表对应的HBase表名,列簇以及列,InputFormat和 OutputFormat类&…

LeetCode刷题记录---贪心算法

😄 跟着Carl哥(公众号:代码随想录)学学贪心算法咯~ 。贪心的本质是选择每一阶段的局部最优,从而达到全局最优。举一个例子: 例如,有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?指定每次拿最大的,最终结果就是拿走最大数额的钱。每次拿最大的就是局…

lego-loam学习笔记(二)

前言: 对于lego-loam中地面点提取部分的源码进行学习。 地面点提取在src/imageProjection.cpp中的函数groundRemoval()。内容比较少,容易理解。 size_t lowerInd, upperInd;float diffX, diffY, diffZ, angle; lowerInd表示低线数的点云; …

python本科毕业设计基于神经网络的虚假评论识别系统源码,含模型及数据

主要函数: 1.corpusprocess原始语料处理函数 2.train_word2vec生成word2vec向量 3.generate_id2wec获得索引的w2id,和嵌入权重embedding_weights 4.prepare_data 数据预处理 完整代码下载地址:python本科毕业设计基于神经网络的虚假评论识别系统源码 代…

MyBatis-Plus知识快速入门

文章目录1.MyBatis-Plus简介2.入门案例2.1开发环境2.2创建测试数据库和表2.3创建SpringBoot工程2.4创建实体类以及lombok的使用2.5添加mapper2.6加入日志功能3.基本的CRUD3.1BaseMapper3.2插入3.3删除3.4修改3.5查询4.通用Service4.1创建Service接口和实现类5.常用注解5.1Table…

Android OpenCV(二)主体识别 位置检测

前言 工作中遇到需要通过OpenCV找到图片主体体积占图片百分比的比例,这里做一个问题解决思路的记录。该方面新手小白,有不对的地方可以评论指出哈 。 重要API Sobel算法 Sobel 计算参考文章 索贝尔算子是计算机视觉领域的一种重要处理方法。 主要用于…