Promise期约函数的实现
创始人
2024-03-12 13:02:21
0

前言

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)); //第一次调用 1p.then(res=>console.log('第二次调用',res)); //第二次调用 1p.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)); //resultconst 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的执行逻辑更加复杂。

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...