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记录进行学习,事半功倍哦

相关推荐
AI大模型-小华32 分钟前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子3 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅3 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni3 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学5 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师5 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc885 小时前
微软EDGE浏览器功能学习
前端·学习·edge
Csvn6 小时前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端
Csvn6 小时前
🎨 CSS @layer:用「层叠优先级」终结样式打架的世纪难题
前端