Promise是如何一步一步构建出来的

一、写在前面:为何需要Promise

关于Promise,首先咱们学习Promise之前一定要问问自己,这个玩意儿解决了什么问题,如下参考:

  1. 回调地狱

在ES6之前的JavaScript里,我们经常会通过回调函数来处理异步请求。当多个异步请求有依赖关系时,就会形成回调地狱,代码会变得很难维护。Promise可以将异步请求链式调用,避免回调地狱。

js 复制代码
// 后面的请求方法,依赖前面的请求结果返回,就会写成这样
ajax(url, () => {
  // 处理结果
  ajax(url1, () => { 
    // 处理结果
    ajax(url2, () => {
      // 处理结果
    });
  });
});

Promise处理后变得清晰和直观,逻辑串行,易读

js 复制代码
fetch(url).then(res => {
  return fetch(url1); // 处理结果
}).then(res1 => {
  return fetch(url2);  // 处理结果
}) .then(res2 => {
  // 处理结果
});

2.其他优势

回调函数的方式无法统一处理中间过程的请求错误,Promise通过catch可以方便地处理Promise链条上任何操作抛出的错误,而且回调函数难以组合多个异步请求的返回结果,而Promise通过then的链式调用,在最后获得汇总的结果。

二、Promise的基础用法

  • Promise 基础用法,有3个状态:等待(PENDING),成功(RESOLVED), 失败(REJECTED)
js 复制代码
let promise = new Promise((resolve, reject) => {
    resolve('执行成功')
}).then((data) => {
    console.log(data); // 成功后的处理逻辑
}, (err) => {
    console.log(err);  // 失败后的处理逻辑
}).finally(() => {
   // 不管成功还是失败都会执行的逻辑,比如接口loading状态可以在这里修改为false
   console.log(err);
})

三、实现自己的Promise

3.1 项目初始化

  • 创建一个空文件夹: mkdir promise-bysking

  • 初始化一个空项目 yarn init -y

  • 新建代码文件

js 复制代码
touch Promise.js // Promise工具文件
touch index.js // 测试代码文件

3.2 搭建项目基础框架

index.js

js 复制代码
// index.js 是一个类 new的时候支持传入一个函数,接受2个参数,拥有then方法,接受两个参数,
let Promise = require('./Promise.js').Promise;
let p = new Promise((resolve, reject) => {
    resolve('执行成功')
}).then((data) => {
    console.log(data);
}, (err) => {
    console.log(err);
})

Promise.js

js 复制代码
// 定义3个状态
const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'

class Promise {
    // 接受一个执行器函数
    constructor (executor) {
        this.status = PENDING;// 状态初始化
        this.value = undefined; // 记录成功状态的值
        this.reason = undefined; // 记录失败原因
        
        // 修改成功状态的函数
        let resolve = (value) => {
            this.value = value;
            this.status = RESOLVED;
        }
        
        // 修改失败状态的函数
        let reject = (reason) => {
            this.reason = reason;
            this.status = REJECTED
        }
        
        // 将修改promise状态的方法提供给执行器函数,注意这个函数是立即执行的!!!!!
        executor(resolve, reject);

    }
    
    // 提供的then方法
    then () {}
}

// 导出Promise工具类
module.exports = {
    Promise
};

3.3 处理promise状态只能被修改一次

js 复制代码
// Promise.js
const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'

class Promise {
    constructor (executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;

        let resolve = (value) => {
            if (this.status === PENDING) { // 状态是pending才能修改为成功状态
                this.value = value;
                this.status = RESOLVED;
            }
        }
        let reject = (reason) => {

            if (this.status === PENDING) {  // 状态是pending才能修改为失败状态
                this.reason = reason;
                this.status = REJECTED
            }
        }
        executor(resolve, reject);

    }
    then () {
    }
}

module.exports = {
    Promise
};

3.4 统一拦截异常报错

咱们业务代码是跑在Promise实例化时传入的executor函数里面,统一try catch后遇到错误直接调用reject方法修改promise状态为失败

3.5 实现then方法

回顾一下,咱们Promise的then方法使用,then方法接受两个函数,分别处理成功和失败两种情况

js 复制代码
let p = new Promise((resolve, reject) => {
    resolve()
}).then((data) => { // 传入的第一个函数,处理执行成功状态的结果
    console.log(data);
}, (err) => { // 传入的第二个函数,处理执行失败状态的结果
    console.log(err);
})

于是我们写出如下代码:

js 复制代码
class Promise {
    // ...省略其他代码
    then (onfulfilled, onreject) {
        if (this.status === RESOLVED) {
            // 成功状态才执行传入的函数,并将promise的值作为参数传递
            onfulfilled && onfulfilled(this.value)
        }

        if (this.status === REJECTED) {
             // 失败状态才执行传入的函数,并将promise的失败原因值作为参数传递
            onreject && onreject(this.reason)
        }
    }
}

3.6 实现then方法支持异步

原来咱们初始化Promise的时候,传入的executor是立马就执行的,试想一下,假如用户在executor里面,将修改Promise状态的逻辑放在了异步任务里面延迟执行,那么3.5小节的then方法里面由于promise的状态还是Pending状态,不满足成功或者失败的条件,两个if条件无法满足

为了解决这个问题,我们需要将同步逻辑做一些调整,我们的思路是:先收集需要执行的函数,等真正需要执行了再取出来执行(按照注释前面的序号看下面这段逻辑)

js 复制代码
class Promise {
    constructor (executor) {
        // ...省略其他代码
        
        
        this.onfulfilledArr = []; // 1.收集成功的处理回调函数
        this.onrejectArr = []; // 2.收集失败的处理回调函数

        let resolve = (value) => {
            if (this.status === PENDING) {
                this.value = value;
                this.status = RESOLVED;
                
                // 6.setTimeout时间到,用户执行了resolve,将订阅列表依次取出执行
                this.onfulfilledArr.forEach(fn => {
                    fn()
                })
            }
        }
        let reject = (reason) => {

            if (this.status === PENDING) {
                this.reason = reason;
                this.status = REJECTED

                // 7.setTimeout时间到,用户执行了reject,将订阅列表依次取出执行
                this.onrejectArr.forEach(fn => {
                    fn();
                })
            }
        }
        
        // ...

    }
    then (onfulfilled, onreject) {
        if (this.status === RESOLVED) {
            onfulfilled && onfulfilled(this.value)
        }

        if (this.status === REJECTED) {
            onreject && onreject(this.reason)
        }
        
        // 3. 用户调用resolve或者reject是在异步任务里面的,就会进入这段逻辑
        if (this.status === PENDING) {
        
            // 4. 收集用户自定义的针对成功状态的处理函数,放入数组
            onfulfilled && this.onfulfilledArr.push(() => {
                onfulfilled(this.value)
            });
            
            // 5. 收集用户自定义的针对失败状态的处理函数,放入数组
            onreject && this.onrejectArr.push(() => {
                onreject(this.reason)
            })
        }
    }
}

module.exports = {
    Promise
};

3.7 处理then的链式调用

promise是支持promise().then().then().(...).then(); 链式调用的,我们看下如何处理这种场景

js 复制代码
let Promise = require('./Promise.js').Promise;
let p = new Promise((resolve, reject) => {
    resolve('执行成功')
}).then((data) => {
    console.log(data);
}).then(data => {
    console.log(data + '111');
})

then方法是在Promise的实例上的,咱们想一下,是不是在then方法执后返回一个Promsie就行?因为返回的Promise上就有then方法

首先我们需要返回一个promsie,同时原来then方法里面的逻辑也要被执行,那不妨将原来then方法里面的逻辑整体搬到新的promise的executor执行器函数里面,因为executo里面的代码也是会立即执行的,两者等效。

其次,我们返回了新的promise,那老的正在执行的promise咋办?我们的方法就是状态继承。resolve,reject是更改promise状态的函数,我们只需要新的Promise2的resolve和reject分别在旧Promise的成功回调以及失败回调的地方执行一下就能继承状态,同时注意x,y分别是上一个promise的成功回调和失败回调的返回值,获取后也会传递给新的promise,这样不仅状态继承,连成功的数据value和失败的原因reason也一起被继承,就能实现多个.the的链式调用了

如上就是源码1-1.5的内容,本期就复习一下之前学习的内容,做一个简单总结输出,后续剩余内容看大家意愿再进行更新咯

我也贴上我的源码,感兴趣自行查看 github.com/bysking/pro...

建议大家通过commit记录进行学习,事半功倍哦

相关推荐
卸任11 小时前
Vibe coding工具使用心得
前端·ai编程
brien912 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance12 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M12 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写12 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李12 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易12 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马12 小时前
前沿视界
前端
kyriewen13 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易13 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员