Promise的运行原理以及重写Promise内置类

1.为什么用?

假设我们需要发三次请求A,B,C,B请求需要用到A请求回来的数据,C需要用到B请求回来的数据,怎么发送?

$.ajax({
    url:'jsonA.json',
    success(data) {
        $.ajax({
            url:'jsonB.json',
            success(data) {
                $.ajax({
                    url:'jsonC.json',
                    success(data) {

                    }
                })
            }
        })
    }
})

这样的代码就是所谓的回调地狱(回调金字塔)了,如果还有DEFG等等请求呢,这么嵌套下去的代码可读性复写性等都太差

那么,怎么解决呢?

1.基于发布订阅来解决
  重点不在发布订阅上,就不上代码,讲讲思路:
  B函数订阅A函数,C函数订阅B函数,A函数执行成功时发布给B,B函数执行成功时发布给C
2.使用Promise来解决回调地狱(回调金字塔)

2.怎么来用?

①:那么,先来认识下什么是Promise:

Promise的运行原理以及重写Promise内置类_第1张图片

promise是用来管理异步的一个内置类,其有三种状态

pedding:就绪状态

resolved(fulfilled):成功状态

rejected:失败状态

 

 ②:promise的then

Promise的运行原理以及重写Promise内置类_第2张图片

 

 ③:promise的执行流程是怎么样的

Promise的运行原理以及重写Promise内置类_第3张图片

以上三张图都是说promise的执行机制原理,但是事实上我们在项目都不会这么用,而是我们手动给其then以及catch返回一个带状态的promise的实例。

那么,还是上面那个需求:

假设我们需要发三次请求A,B,C,B请求需要用到A请求回来的数据,C需要用到B请求回来的数据,怎么发送?

使用promise来实现一下看看:

  // 先将A,B,C封装成三个方法,手动给方法返回promise对象,
    let queryA = () => {
        return new Promise((resolve) => {
            $.ajax({
                url:'jsonA.json',
                success(data) {
                    resolve(data);
                }
            })
            }
        )
    };
    let queryB = (result) => {
        return new Promise((resolve) => {
                $.ajax({
                    url:'jsonB.json',
                    success(data) {
                           resolve(data);
                    }
                })
            }
        )
    }
    let queryC = (result) => {
        return new Promise((resolve) => {
                $.ajax({
                    url:'jsonC.json',
                    success(data) {
                        console.log(data);
                    }
                })
            }
        )
    }
    let p = queryA();
    p.then(queryB).then(queryC);

 

这时候就会有小伙伴疑惑了,咦,这个promise的实现跟上面原理中的promise实现完全不同哎,小编你确定你没在逗我?

Promise的运行原理以及重写Promise内置类_第4张图片

 

那么,再看看这张图呢

Promise的运行原理以及重写Promise内置类_第5张图片

那么现在明白了promise的一些运行的原理了,那么如果我们的B请求不依赖于A请求,C请求依赖于A和B请求呢?

 promise的原型上为我们提供了一个all方法(状态全部为fulfilled时才改变实例的状态为fulfilled)

let p=Promise.all([queryA(),queryB()]);
    p.then(queryC);
queryA和queryB全部执行完毕且返回的两个promise实力的状态都是fulfilled的时候p的状态才变为fulfilled, 如果有一个状态是rejected那么p的状态就是rejected,queryA和queryB返回值会合并成一个数组传给queryC 
promise的原理就到这了,那么既然懂了其原理,我们能不能自己写一个promise出来?

3.my Promise

先请大家认真看一张图便于理解后面重写的promise代码

Promise的运行原理以及重写Promise内置类_第6张图片

如果你认真看了上面的图,我相信你能理解下面的代码 

class MyPromise {
    // excutor执行器,就是我们new的时候传进来的函数
    constructor(excutor){
        // 实例状态
        this.status = 'pending';
        // 成功状态事件池
        this.fulfilledCallbacks = [];
        // 失败状态事件池
        this.rejectedCallbacks = [];
        // 记录执行resolve和reject时的参数
        this.value = undefined;

        let resolve = (result) => {
            // 如果不是pending那么状态就已经凝固,不能更改
            if(this.status === 'pending'){
                this.status = 'resolved';
                this.value = result;
                // 为了避免还没有执行then,成功状态事件池还没添加这里就同步执行了,因为foreach是一个同步方法
                // 所以需要把循环事件池处理成异步的,考虑到then也是异步的,所以不仅要将这里处理成异步的,还要
                // 要求这里的异步在then的异步后面执行,then是微任务,加个定时器让其变成宏任务,就一定时在then
                // 后面被执行
                let time = setTimeout(() => {
                    clearTimeout(time);
                    // 循环执行成功状态事件池中的事件
                    this.fulfilledCallbacks.forEach(item => item(this.value))
                },0)
            }
        };
        let reject = (reason) => {
            if(this.status === 'pending'){
                this.status = 'rejected';
                this.value = reason;
                let time = setTimeout(() => {
                    clearTimeout(time);
                    this.rejectedCallbacks.forEach(item => item(this.value))
                },0)
            }
        };

        try {
            excutor(resolve,reject);
        }catch (e) {
            reject(e);
        }

    }

    // then方法就是重点了,传入两个回调函数,但是并不立即执行,而是等this的status状态发生变化
    // 时在其resolve或者reject方法中执行
    then(onFulfilled,onRejected){
        // 需要链式调用then方法,所以then方法的返回值必须是一个promise的实例,而resolve和reject
        // 两个形参则是用来改变返回的这个promise的状态的,因为下一个then中的回调还挂着返回这个promise
        // 对象的身上,需要通过控制resolve和reject的执行来控制下一个then中执行的方法
        return new MyPromise((resolve,reject) => {
            // 向成功状态事件池添加回调,只添加并没有执行,this指向上一个promise实例,
            // 什么时候执行?在调用this的resolve方法时被执行,result就是传进来的this.value
            this.fulfilledCallbacks.push((result) => {
                // 如果then中的方法执行时报错,直接执行返回的promise的reject方法触发下一个then的失败回调执行
               try {
                   // onFulfilled是什么?是then中的第一个方法,result是上一个promise对象传过来的参数
                   // onFulfilled(result)执行的返回值如果是一个普通值,就通知下一个then中的成功回调执行
                   // 这个普通值作为参数传进去
                   let x = onFulfilled(result);
                   x instanceof MyPromise
                   // 如果返回的是一个promise对象,那就需要用返回的promise对象的状态来控制本次返回的promise
                   // 对象的状态以达到控制下一个then中哪个回调的执行,这里很妙的用了then方法,x是then中回调返
                   // 回的promise对象,resolve和reject两个实参则是控制当前返回peomise对象状态的方法,这样如
                   // 果then回调返回的promise状态时resolve就会执行当前返回promise对象的resolve以触发下
                   // 一个then中的成功回调的执行,如果then中回调的状态为reject就会执行传进去的reject也就
                   // 是控制当前返回promise对象状态的reject,就可以触发下一个then中的失败回调的执行
                   ? x.then(resolve,reject)
                   : resolve(x);
               }catch (e) {
                   reject(e);
               }
            })
            // 向失败状态事件池添加回调,原理与成功事件池添加一样
            this.rejectedCallbacks.push((reason) => {
                try {
                    let x = onRejected(reason);
                    x instanceof MyPromise
                    ? x.then(resolve,reject)
                    : resolve(x)
                }catch (e) {
                    reject(e);
                }
            })
        })
    }

}






// 测试代码
new MyPromise((resolve,reject) => {
    console.log(1);
    reject();
}).then((res) => {
    console.log(2);
},(err) => {
    console.log(3);
}).then((res) => {
    console.log(4);
},(err) => {
    console.log(5);
});

每一步都写有注释,细节不懂的可以评论 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(javascript)