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

相关推荐
qq_364371721 小时前
Vue 内置组件 keep-alive 中 LRU 缓存淘汰策略和实现
前端·vue.js·缓存
y先森2 小时前
CSS3中的弹性布局之侧轴的对齐方式
前端·css·css3
y先森7 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy7 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189117 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿8 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡9 小时前
commitlint校验git提交信息
前端
虾球xz9 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇9 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒10 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript