浅谈promise原理及实现

613 阅读12分钟
一、js中的同步api与异步api
1.同步api:
    只有当前api执行完成之后才会继续向下执行
    同步执行的api可以获取到前面执行的api的返回结果
2.异步api:
    异步api可以同时执行,不会互相阻塞
    异步api获取不到代码顺序上前面的api的返回结果
    
    
    举个栗子:  
    //  同步
    function fn(num1, num2) {
      return num1 + num2;
    }
    let num = fn(1, 2) //  num的值为3
    
    function fn2() {
        setTimeout(function () {
            return 'hellow js'
        }, 1000)
    }

    let str = fn2() // 值为undefined

但是通过回调函数可以取得异步执行的结果

    function fn(cb) {
        setTimeout(function () {
            let str = '异步回调'
            cb(str)
        }, 1000)
    }
    fn((str) => {
        console.log(str) // 异步回调
    })

异步代码执行区的异步api执行完成,将要执行专属的回调函数时,就会将回调函数放入回调函数队列,等同步代码执行区的代码执行完成后,就把回调函数队列的回调函数加入同步代码执行区。

定时器 ajax请求,都是异步执行

二、promise出现的原因及需求

1.我们在执行api时,通常会用到上一个api的执行结果

const btn = document.getElementById('btn')
    btn.addEventListener('click', () => {
        // 创建一个request
        const request = new XMLHttpRequest();
        // 初始化请求
        request.open('get', 'https://xxx/xxx/xxx:xxxx')
        // 发送请求
        request.send();
        // 处理返回参数
        request.onreadystatechange = function () {
            //  判断请求状态码
            if (request.readyState === 4) {
                if (request.status === 200) {
                    // 输出返回参数
                    console.log(request.response);
                    // 在没有使用promise时,如果我们想要使用此函数的返回结果
                    // 就需要在这里进行回调
                    request.open('get', `https://xxx/xxx/xxx:xxxx?xx=${request.response}`)
                    // 发送请求
                    request.send();
                    request.onreadystatechange = function () {
                        
                    }
                }
            }
        }
    })

此时,如果有a,b,c...等若干个函数,切,下一个函数总是依赖上一个函数的执行结果,就会一直嵌套调用,也就是我们所说的回调地狱。

如果我们使用promise封装一个ajax请求

    function requestAjax(url) {
        return new Promise((resolve, reject) => {
            const request = new XMLHttpRequest();
            request.open('GET', url);
            request.send();
            request.onreadystatechange = function () {
                if (request.readyState === 4) {
                    if (request.status = 200) {
                        resolve(request.response);
                    } else {
                        reject(request.status)
                    }
                }
            }
        })
    }
    requestAjax('xxx/xxx/xxxx:xxxx').then(() => {
        // 在这里进行下一步执行的函数处理
    }).catch(() => {

    })

当我们使用promise去封装一个ajax请求后,这样可以避免回调地狱,让我们代码的可读性更高。

三、promise的状态、属性、基本流程

1.状态

promise的状态是promise对象中的一个属性

  • pending 进行中
  • resolved / fulfilled 成功
  • rejected 失败 promiseState.png

状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态

promise中另一个属性保存的是异步任务的执行结果 [[PromiseResult]]

promiseResult.png

2.流程

promise流程.png

四、手写一个promise
1.定义一个Promise框架
// 定义一个promise函数并添加一个then方法
function Promise(executor) {
    
}

// 给promise添加 then 方法
Promise.prototype.then = function (onResolved, onRejected) {

}
2.封装resolve和reject
// 封装resolve和reject
function Promise(executor) {
    // resolve 函数
    resolve = (data) => {}
    // reject 函数
    reject = (data) => {}
    // 同步调用 [执行器函数]
    executor(resolve, reject);
}
3.resolve和reject的实现及添加状态和属性
// resolve和reject的实现
function Promise(executor) {
    // 添加属性用来保存状态
    this.PromiseState = 'pending';
    // 添加属性用来保存结果
    this.PromiseResult = null;
    // resolve 函数
    resolve = (data) => {
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'fulfilled';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
    }

    // reject 函数
    reject = (data) => {
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'rejected';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
    }

    // 同步调用 [执行器函数]
    executor(resolve, reject);
}
4.抛出异常,改变状态
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    // resolve 函数
    resolve = (data) => {
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'fulfilled';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
    }

    // reject 函数
    reject = (data) => {
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'rejected';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
    }
    // 抛出异常 并改变当前状态
    try {
        // 同步调用 [执行器函数]
        executor(resolve, reject);
    } catch (error) {
        // 修改 promise 对象状态为  失败
        reject(error)
    }
}
5.promise的状态只允许修改一次,这里加一个判断就好,看下当前的状态
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;

    // resolve 函数
    resolve = (data) => {
        if (this.PromiseState !== 'pending') return;
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'fulfilled';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
    }

    // reject 函数
    reject = (data) => {
        if (this.PromiseState !== 'pending') return;
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'rejected';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
    }
    // 抛出异常 并改变当前状态
    try {
        // 同步调用 [执行器函数]
        executor(resolve, reject);
    } catch (error) {
        // 修改 promise 对象状态为  失败
        reject(error)
    }
}
6.then方法执行回调
Promise.prototype.then = function (onResolved, onRejected) {
    // 判断当前执行的状态 
    if (this.PromiseState === 'fulfilled') {
        onResolved(this.PromiseResult);
    }
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
}
7.异步任务回调的执行
前面我们说的都是在函数执行器同步执行的情况下,所以在then方法中我们可以获取到PromiseState和PromiseResult的值,但是如果要是在executor函数执行器中进行异步调用then方法是不能直接获取到PromiseState和PromiseResult的值的,但是then方法的执行又需要依赖于PromiseState与PromiseResult的值,该如何处理。
官方的解决方案是,在then方法中先判断一下当前的状态,如果是pending,证明异步函数还没有执行完成,这时不可以直接调用then中的回调函数,可以先把回调函数作为promise中的一个属性保存起来。

function Promise(executor) { 
// 声明一个属性,用来保存 then 中的回调函数 
this.callback = {} 
}

在pending时将回调保存

其实这时候 then 方法已经结束了,没有把回调函数进行调用,所以就先把回调函数存到 p 这个对象的 callback 上。在异步任务结束后,交给window 托管的 resolve 开始执行,这个 window 托管的函数使用了 p 对象中存着的 callback 函数。

我们新建一个html来调用promise

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="btn">
    </div>
</body>
<script src="./promise.js"></script>
<script>
    let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('err')
            }, 1000)
        })


        p.then(res => {
            console.log(res);
        }, reason => {
            console.log(reason);
        })

        console.log(p);
</script>

</html>

在then中保存回调

Promise.prototype.then = function(onResolved, onRejected) {
    // 判断 pending 状态
    if (this.PromiseState === 'pending') {
        // 保存回调函数
        this.callback = {
            onResolved: onResolved,
            onRejected: onRejected
        }
    }
}

异步函数执行完成之后,调用回调函数

    // resolve 函数
    function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;

    // resolve 函数
    resolve = (data) => {
        if (this.PromiseState !== 'pending') return;
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'fulfilled';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
        // 调用onResolved
        if (this.callback.onResolved) {
            this.callback.onResolved(data);
        }
    }

    // reject 函数
    reject = (data) => {
        if (this.PromiseState !== 'pending') return;
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'rejected';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
        // 调用onRejected
        if (this.callback.onRejected) {
            this.callback.onRejected(data);
        }
    }
    // 抛出异常 并改变当前状态
    try {
        // 同步调用 [执行器函数]
        executor(resolve, reject);
    } catch (error) {
        // 修改 promise 对象状态为  失败
        reject(error)
    }
}
8.执行多个回调函数

我们希望上述的两个回调函数都可以执行,但是当我们向callback属性存储回调函数时,后面的回调就会将前面的回调给覆盖,这时,我们可以采用数组存储回调函数,执行时再将数组中的方法进行遍历。

// 1. 定义一个promise函数
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;

    // resolve 函数
    resolve = (data) => {
        if (this.PromiseState !== 'pending') return;
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'fulfilled';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
        // 调用onResolved
        this.callback.forEach(item => {
            item.onResolved(data);
        });
    }

    // reject 函数
    reject = (data) => {
        if (this.PromiseState !== 'pending') return;
        // 1. 修改对象的状态 (promiseState)
        this.PromiseState = 'rejected';
        // 2. 设置对象结果值 (promiseResult)
        this.PromiseResult = data;
        // 调用onRejected
        this.callback.forEach(item => {
            item.onRejected(data);
        });
    }
    // 抛出异常 并改变当前状态
    try {
        // 同步调用 [执行器函数]
        executor(resolve, reject);
    } catch (error) {
        // 修改 promise 对象状态为  失败
        reject(error)
    }
}

// 2. 给promise添加 then 方法
Promise.prototype.then = function (onResolved, onRejected) {
    console.log(this)
    // 调用回调函数 PromiseState
    if (this.PromiseState === 'fulfilled') {
        onResolved(this.PromiseResult);
    }
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
    // 判断 pending 状态
    if (this.PromiseState === 'pending') {
        // 保存回调函数
        this.callback.push({
            onResolved: onResolved,
            onRejected: onRejected
        });
    }
}
9.then方法返回的结果,同步修改

我们在上面说到,then方法会返回一个新的promise对象,返回的具体内容与回调函数的return有关

这里先进行一个返回值为非promise对象的情况

在调用promise时

    let p = new Promise((resolve, reject) => {
        resolve('Ok')
    })

    let str = p.then(value => {
        return '111'
    })
    console.log(str);

而在then方法中,我们需要返回一个promise对象

Promise.prototype.then = function (onResolved, onRejected) {
    // 返回 promise
    return new Promise((resolve, reject) => {
        // 调用回调函数 PromiseState
        if (this.PromiseState === 'fulfilled') {
            // 所以这个result就是回调函数返回的结果
            let result = onResolved(this.PromiseResult);
            // 如果返回的是 promise 对象
            if (result instanceof Promise) {
                // 这里先不处理
            } else { // 返回的是常规值就是成功
                // 要将 outerResult 这个 promise 目前的状态 pending 改成fulfilled
                // 通过 resolve 就可以
                resolve(result);
            }
        }
    })
   }

控制台看一下结果

promise.png

这里,outerResult为p.then() 返回 的 promise 对象,在 p.then() 内的回调函数返回的 promise 对象为 result

再看一下回调函数是promise的情况

Promise.prototype.then = function (onResolved, onRejected) {
    // 返回 promise
    return new Promise((resolve, reject) => {
        // 调用回调函数 PromiseState
        if (this.PromiseState === 'fulfilled') {
            try {
                // 获取回调函数的结果
                let result = onResolved(this.PromiseResult);
                // 如果返回的是 promise 对象
                if (result instanceof Promise) {
                    // result 执行的是成功,就要给外层的 outerResult 成功的效果
                    result.then(value => {
                        // 使 outerResult 的 状态 和 result 的状态一致
                        resolve(value);
                    }, reason => {
                        reject(reason)
                    })
                } else { // 返回的是常规值就是成功
                    // 要将这个 大 的 promise 目前的状态 pending 改成fulfilled
                    // 通过 resolve 就可以
                    resolve(result);
                }
            } catch (error) {
                reject(error);
            }

        }
        if (this.PromiseState === 'rejected') {
            onRejected(this.PromiseResult);
        }
        // 判断 pending 状态
        if (this.PromiseState === 'pending') {
            // 保存回调函数
            this.callback.push({
                onResolved: onResolved,
                onRejected: onRejected
            });
        }
    })
}

调用

let p = new Promise((resolve, reject) => {
        resolve('111');
    })
    let outerResult = p.then(value => {
        let result = new Promise((resolve, reject) => {
            resolve('222');
        })
        return result;
    })

    console.log(outerResult)

看下结果

promise.png

10.异步修改then返回

前面写的代码,在碰到executor 里是异步函数时,就是将回调函数进行保存,等异步函数执行完,再调用回调函数。但是,如果then方法返回的是一个promise对象,如果直接将回调函数保存再调用的话,返回的promise对象将会一直处于pending状态。

上代码

Promise.prototype.then = function (onResolved, onRejected) {
    // 返回 promise
    return new Promise((resolve, reject) => {
        // 判断 pending 状态
        if (this.PromiseState === 'pending') {
            // 保存回调函数
            this.callback.push({
                onResolved: function (data) {
                    // console.log('success');
                    // 执行成功的回调函数
                    // 获取回调函数返回结果
                    try {
                        let res = onResolved(data);
                        if (res instanceof Promise) {
                            // 根据 回调函数返回的 promise 决定
                            res.then(value => {
                                // 这个 回调函数 返回的 promise 内部调用的是 resolve
                                resolve(value);
                            }, reason => {
                                reject(reason);
                            })
                        } else {
                            // 不是 promise 返回的就是正确
                            // 改变outerResult的状态
                            resolve(res);
                        }
                    } catch (error) {
                        reject(error)
                    }

                },
                onRejected: function (data) {
                    try {
                        // 改变 promise 的状态为 rejected
                        let res = onRejected(data);
                        if (res instanceof Promise) {
                            // 根据 回调函数返回的 promise 决定
                            res.then(value => {
                                // 这个 回调函数 返回的 promise 内部调用的是 reject
                                resolve(value);
                            }, reason => {
                                reject(reason);
                            })
                        } else {
                            // 改变outerResult的状态
                            reject(res);
                        }
                    } catch (error) {
                        reject(error)
                    }
                }
            });
        }
    })
}
11.catch方法

前面的then方法已经比较完善了,所以我们在这里直接调用一下then方法

Promise.prototype.catch = function (onRejected) { return this.then(undefined, onRejected); }

在promise中,then的链式调用,可以指定失败的回调,前面任何操作出现异常,都会传到最后失败处理的回调函数中

executor 执行器在执行它内部异步代码前,同步代码已经执行结束了,也就是 p.then() 执行完毕,把 then 内的回调方法存到了 p 的自身属性上。而 then 的回调参数有两个:onResolved,onRejected。如果我们只传一个参数就会这样

let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('Err');
        }, 1000)
    })


    let res = p.then(value => {
        console.log(111);
    }).then(value => {
        console.log(222);
    }).then(value => {
        console.log(333);
    }).catch(reason => {
        console.log(reason);
    })

promise.png

这是因为,在这个栗子中只传了一个 onResolve,没有onRejected,所以 保存在 p 本身上的回调函数 onRejected 就为空。 所以,我们需要在then方法中加入一个onRejected回调函数

Promise.prototype.then = function (onResolved, onRejected) {
    // 返回 promise
    return new Promise((resolve, reject) => {
        // 判断 pending 状态
        if (this.PromiseState === 'pending') {
            // 判断回调函数的参数
            // then 方法中并没有 onRejected 这个回调方法
            if (typeof onRejected !== 'function') {
                // 手动给 then 添加这个回调函数 
                onRejected = reason => {
                    // 抛异常
                    throw reason;
                }
            }
            // 保存回调函数
            this.callback.push({
                onResolved: function (data) {
                    // console.log('success');
                    // 执行成功的回调函数
                    // 获取回调函数返回结果
                    try {
                        let res = onResolved(data);
                        if (res instanceof Promise) {
                            // 根据 回调函数返回的 promise 决定
                            res.then(value => {
                                // 这个 回调函数 返回的 promise 内部调用的是 resolve
                                resolve(value);
                            }, reason => {
                                reject(reason);
                            })
                        } else {
                            // 不是 promise 返回的就是正确
                            // 改变outerResult的状态
                            resolve(res);
                        }
                    } catch (error) {
                        reject(error)
                    }

                },
                onRejected: function (data) {
                    try {
                        // 改变 promise 的状态为 rejected
                        let res = onRejected(data);
                        if (res instanceof Promise) {
                            // 根据 回调函数返回的 promise 决定
                            res.then(value => {
                                // 这个 回调函数 返回的 promise 内部调用的是 reject
                                resolve(value);
                            }, reason => {
                                reject(reason);
                            })
                        } else {
                            // 改变outerResult的状态
                            reject(res);
                        }
                    } catch (error) {
                        reject(error)
                    }
                }
            });
        }
    })
}

然后catch就可以接收这个异常了

promise.png

值传递的话就是在then方法中加一个 onResolved 回调就行了

Promise.prototype.then = function (onResolved, onRejected) {
    // 返回 promise
    return new Promise((resolve, reject) => {
        // 判断 pending 状态
        if (this.PromiseState === 'pending') {
            // 判断回调函数的参数
            // then 方法中并没有 onRejected 这个回调方法
            if (typeof onRejected !== 'function') {
                // 手动给 then 添加这个回调函数 
                onRejected = reason => {
                    // 抛异常
                    throw reason;
                }
            }
            // then 方法中并没有 onResolved 这个回调方法
            if (typeof onResolved !== 'function') {
                // 手动给 then 添加这个回调函数 
                onResolved = value => {
                    return value;
                }
            }
            // 保存回调函数
            this.callback.push({
                onResolved: function (data) {
                    // console.log('success');
                    // 执行成功的回调函数
                    // 获取回调函数返回结果
                    try {
                        let res = onResolved(data);
                        if (res instanceof Promise) {
                            // 根据 回调函数返回的 promise 决定
                            res.then(value => {
                                // 这个 回调函数 返回的 promise 内部调用的是 resolve
                                resolve(value);
                            }, reason => {
                                reject(reason);
                            })
                        } else {
                            // 不是 promise 返回的就是正确
                            // 改变outerResult的状态
                            resolve(res);
                        }
                    } catch (error) {
                        reject(error)
                    }

                },
                onRejected: function (data) {
                    try {
                        // 改变 promise 的状态为 rejected
                        let res = onRejected(data);
                        if (res instanceof Promise) {
                            // 根据 回调函数返回的 promise 决定
                            res.then(value => {
                                // 这个 回调函数 返回的 promise 内部调用的是 reject
                                resolve(value);
                            }, reason => {
                                reject(reason);
                            })
                        } else {
                            // 改变outerResult的状态
                            reject(res);
                        }
                    } catch (error) {
                        reject(error)
                    }
                }
            });
        }
    })
}

12.promise.all方法的封装

比较简单,直接上代码

Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        // 声明变量
        let count = 0;
        let arr = [];
        // 遍历
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(v => {
                // 对象的状态是成功
                count++;
                // 将当前 promise 对象成功的结果 存到数组中
                arr[i] = v;
                // 判断如果全都成功,就返回成功
                if (count === promises.length) {
                    // 修改状态
                    resolve(arr);
                }
            }, r => {
                reject(r);
            })
        }
    })
}

示例

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('111')
    }, 500)
})

let p2 = new Promise((resolve, reject) => {
    resolve('222')
});

let p3 = new Promise((resolve, reject) => {
    resolve('333')
});

let res = Promise.all([p1, p2, p3])
console.log(res);

五、这样,一个完整的promise基本已经成型了。