乐趣区

关于javascript:Promise全解

Promise 简介

是一个保留了异步事件将来执行后果的对象。它不是一个新技术,而是一种能够 优化异步编程编码格调的标准 。最早诞生于社区,用于解决 JavaScript 代码中 回调嵌套层级过多的问题和错误处理逻辑堆砌的问题。应用 Promise 对象封装异步操作,能够让代码变得直观、线性、优雅。

Promise 根本应用

用 Promise 封装 Ajax 申请

// 先写一个原始的 Ajax 申请
let xhr = new XMLHttpRequest()
function resolve(v){console.log(v);}
function reject(e){console.log(e);}
xhr.onerror = function(e){reject(e)
}
xhr.ontimeout = function(e){reject(e)
}
xhr.onreadystatechange = function(){if(xhr.readyState===4){if(xhr.status===200){resolve(xhr.response)
        }
    }
}
xhr.open('Get','https://wwww.google.com',true)
xhr.send()

// 第一版:利用 Promise 封装 ajax
let p = new Promise((resolve,reject)=>{let xhr = new XMLHttpRequest()
    xhr.onerror = function(e){reject(e)
    }
    xhr.ontimeout = function(e){reject(e)
    }
    xhr.onreadystatechange = function(){if(xhr.readyState===4){if(xhr.status===200){resolve(xhr.response)
            }
        }
    }
    xhr.open('Get','https://wwww.google.com',true)
    xhr.send()});

p.then(v=>{console.log("我是胜利时注册的回调");
},e=>{console.log("我是失败时注册的回调");
})

// 第二版 反对传参、封装了 Promise 创立的细节
function Xpromise(request){function executor(request,resolve,reject){let xhr = new XMLHttpRequest()
        xhr.onerror = function(e){reject(e)
        }
        xhr.ontimeout = function(e){reject(e)
        }
        xhr.onreadystatechange = function(){if(xhr.readyState===4){if(xhr.status===200){resolve(xhr.response)
                }
            }
        }
        xhr.open('Get',request.url,true)
        xhr.send()}
    renturn new Promise(executor);
}

let x1 = Xpromise(makeRequest('https://wwww.google.com')).then(v=>{console.log("我是胜利时注册的回调");
},e=>{console.log("我是失败时注册的回调");
})

另外,Promise 还提供了一系列好用的 API, 如动态 resolve()、all()、race()办法等。

实现原理概述

Promise 用 回调函数提早绑定 回调函数 onResolve 返回值穿透 机制解决回调嵌套层级过多的问题;应用 谬误冒泡机制 简化了错误处理逻辑堆砌的问题。

手工实现一个 Promise

第一版

// 第一点:Promise 是一个类
class MyPromise {
    // 第二点:Promised 构造函数的参数是一个函数;constructor(fn) {if (typeof fn !== "function") {throw new Error("promise 的结构函数参数应该为函数类型")
        }
        // 第三点:Promise 的外部状态有三个,Promise 对象具备值
        this._status = PENDING;
        this._value = undefined;
        // 第五点:new Promise(fn)时,就须要执行 fn 做业务逻辑,故构造函数里就要调用 fn 函数。此处外部函数_resolve 和_reject 会被调用用于追踪 Promise 的外部状态
        try {
            // 留神用 try-catch 包住
            fn(this._resolve.bind(this), this._reject.bind(this));
        } catch (err) {this._reject(err)
        }
    }
    // 第四点:定义 MyPromise 状态翻转时,要执行的外部函数
    _resolve(val){if (this._status !== this.PENDING) return // 这段代码体现了 Promise 的状态翻转:只能是 P ->F 或者是 P ->R
        this._status = FULLFILLED;
        this._value = val;
    };
    _reject(err){if (this._status !== this.PENDING) return
        this._status = REJECTED;
        this._value = err;
    };
}

第二版

class MyPromise {constructor(fn) {if (typeof fn !== "function") {throw new Error("myPromise 的结构函数参数应该为函数类型")
        }
        this._status = PENDING;
        this._value = undefined;
        // 个性 2 - 新增定义两个回调函数数组
        this.fulfilledQueue = [];
        this.rejectedQueue = [];
        try {fn(this._resolve.bind(this), this._reject.bind(this));
        } catch (err) {this._reject(err)
        }
    }
    _resolve(val){if (this._status !== this.PENDING) return
        // 个性 4:注册的回调函数在 Promise 状态翻转时会执行,执行的形式是循环从队列外面取出回调执行
        // 定义 run 函数
        run = (value)=>{
            this._status = FULLFILLED;
            this._value = val;
            let ck;
            while(ck = this.fulfilledQueue.shift()){ck(value);
            }
        }
        // 个性 5:run()函数的执行,这里十分要害。要把 run 放进 setTimeOut。为什么?// 因为执行_resolve()函数时,then()可能还没执行,所以为了让 then()中的回调、包含链式调用的 then()的回调增加到 fulfilledQueue 中,// 须要提早执行 run()。实际上这里用 setTimeout 性能差,理论中采纳微工作的形式实现
        setTimeout(run,0)
        //run();};
    _reject(err){if (this._status !== this.PENDING) return
        run = (error)=>{
            this._status = this.REJECTED;
            this._value = err;
            let ck;
            while(ck = this.rejectedQueue.shift()){ck(error)
            }
        }
        setTimeout(run,0);
    };

    // 最重要的 then 函数:// 个性 1 - 用于注册回调,then()函数有两个参数,都是可选的,如果参数不是函数将会被疏忽
    // then()反对被同一个 Promise 屡次注册,个性 2 - 所以 Promise 外部要保护两个数组,别离存储 then 上注册的胜利回调和失败回调);// then()反对链式调用, 个性 3 - 之所以反对是因为其返回值是一个新的 Promise,此处要实现回调函数 onFulfilled 穿透机制的实现
    
    // 个性 1 - 回调函数的注册: 故为它传递两个回调函数占位
    then(onFulfilled,onRejected){const {_status, _value} = this;
        // 个性 3 - 返回一个新的 promise
        return new MyPromise((onFulfilledNext, onRejectedNext)=>{
            let fulfilled = value => {
                try{if(typeof onFulfilled != "function"){
                        // 如果 onFulfilled 或 onRejected 不是函数,onFulfilled 或 onRejected 被疏忽
                        onFulfilledNext(value)
                    }else{
                        // 如果 onFulfilled 或者 onRejected 返回一个值 res
                        let res = onFulfilled(value)
                        if(res instanceof MyPromise){// 若 res 为 Promise,这时后一个回调函数,就会期待该 Promise 对象 (即 res) 的状态发生变化,才会被调用,并且新的 Promise 状态和 res 的状态雷同
                            res.then(onFulfilledNext, onRejectedNext)
                        }else{
                            // 若 res 不为 Promise,则使 res 间接作为新返回的 Promise 对象的值
                            onFulfilledNext(res)
                        }
                    }
                }catch(err){onRejectedNext(err)
                }
            }
            let rejected = error => {
                try{if(typeof onRejected != "function"){onRejectedNext(error)
                    }else{let res = onRejectedNext(error)
                        if(res instanceof MyPromise){res.then(onFulfilledNext, onRejectedNext)
                        }else{onFulfilledNext(res);
                        }
                    }
                }catch(err){onRejectedNext(err)
                }
            }
            switch(_status){
                case PENDING:
                    // 在 promise 状态扭转前, onFulfilled 或者 onRejected 不可被调用
                    this._fulfilledQueue.push(onFulfilled)
                    this._rejectedQueue.push(onRejected)
                    break
                case FULFILLED:
                    //onFulfilled 调用次数只有一次,fulfilled 对 onFulFilled 进行包装。why need 包装?因为 onFulFilled 可能是函数、可能不是,如果是函数,返回值可能是 Promise 可能不是
                    fulfilled(_value)
                    break
                case REJECTED:
                    //onRejected 调用次数只有一次
                    rejected(_value)
                    break
            }
        })
    }
}

第三版

class MyPromise {constructor(handle) {if (!isFunction(handle)) {throw new Error('MyPromise must accept a function as a parameter')
        }
        this._status = PENDING
        this._value = undefined
        this._fulfilledQueues = []
        this._rejectedQueues = []
        try {handle(this._resolve.bind(this), this._reject.bind(this))
        } catch (err) {this._reject(err)
        }
    }
    // 增加 resovle 时执行的函数
    _resolve(val) {const run = () => {if (this._status !== PENDING) return
            const runFulfilled = (value) => {
                let cb;
                while (cb = this._fulfilledQueues.shift()) {cb(value)
                }
            }
            const runRejected = (error) => {
                let cb;
                while (cb = this._rejectedQueues.shift()) {cb(error)
                }
            }
            /* 
              如果 resolve 的参数为 Promise 对象,则必须期待该 Promise 对象状态扭转后,
              以后 Promsie 的状态才会扭转,且状态取决于参数 Promsie 对象的状态,因而这里进行逻辑辨别
            */
            if (val instanceof MyPromise) {
                val.then(value => {
                    this._value = value
                    this._status = FULFILLED
                    runFulfilled(value)
                }, err => {
                    this._value = err
                    this._status = REJECTED
                    runRejected(err)
                })
            } else {
                this._value = val
                this._status = FULFILLED
                runFulfilled(val)
            }
        }
        setTimeout(run, 0)
    }
    _reject(err) {if (this._status !== PENDING) return
        const run = () => {
            this._status = REJECTED
            this._value = err
            let cb;
            while (cb = this._rejectedQueues.shift()) {cb(err)
            }
        }
        setTimeout(run, 0)
    }
    then(onFulfilled, onRejected) {const { _value, _status} = this
        return new MyPromise((onFulfilledNext, onRejectedNext) => {
            let fulfilled = value => {
                try {if (!isFunction(onFulfilled)) {onFulfilledNext(value)
                    } else {let res = onFulfilled(value);
                        if (res instanceof MyPromise) {res.then(onFulfilledNext, onRejectedNext)
                        } else {onFulfilledNext(res)
                        }
                    }
                } catch (err) {onRejectedNext(err)
                }
            }
            let rejected = error => {
                try {if (!isFunction(onRejected)) {onRejectedNext(error)
                    } else {let res = onRejected(error);
                        if (res instanceof MyPromise) {res.then(onFulfilledNext, onRejectedNext)
                        } else {onFulfilledNext(res)
                        }
                    }
                } catch (err) {onRejectedNext(err)
                }
            }
            switch (_status) {
                case PENDING:
                    this._fulfilledQueues.push(fulfilled)
                    this._rejectedQueues.push(rejected)
                    break
                case FULFILLED:
                    fulfilled(_value)
                    break
                case REJECTED:
                    rejected(_value)
                    break
            }
        })
    }
}

第四版 finally

class MyPromise {constructor(handle) {if (!isFunction(handle)) {throw new Error('MyPromise must accept a function as a parameter')
        }
        this._status = PENDING
        this._value = undefined
        this._fulfilledQueues = []
        this._rejectedQueues = []
        try {handle(this._resolve.bind(this), this._reject.bind(this))
        } catch (err) {this._reject(err)
        }
    }
    _resolve(val) {const run = () => {if (this._status !== PENDING) return
            const runFulfilled = (value) => {
                let cb;
                while (cb = this._fulfilledQueues.shift()) {cb(value)
                }
            }
            const runRejected = (error) => {
                let cb;
                while (cb = this._rejectedQueues.shift()) {cb(error)
                }
            }
            if (val instanceof MyPromise) {
                val.then(value => {
                    this._value = value
                    this._status = FULFILLED
                    runFulfilled(value)
                }, err => {
                    this._value = err
                    this._status = REJECTED
                    runRejected(err)
                })
            } else {
                this._value = val
                this._status = FULFILLED
                runFulfilled(val)
            }
        }
        setTimeout(run, 0)
    }
    _reject(err) {if (this._status !== PENDING) return
        const run = () => {
            this._status = REJECTED
            this._value = err
            let cb;
            while (cb = this._rejectedQueues.shift()) {cb(err)
            }
        }
        setTimeout(run, 0)
    }
    then(onFulfilled, onRejected) {const { _value, _status} = this
        return new MyPromise((onFulfilledNext, onRejectedNext) => {
            let fulfilled = value => {
                try {if (!isFunction(onFulfilled)) {onFulfilledNext(value)
                    } else {let res = onFulfilled(value);
                        if (res instanceof MyPromise) {res.then(onFulfilledNext, onRejectedNext)
                        } else {onFulfilledNext(res)
                        }
                    }
                } catch (err) {onRejectedNext(err)
                }
            }
            let rejected = error => {
                try {if (!isFunction(onRejected)) {onRejectedNext(error)
                    } else {let res = onRejected(error);
                        if (res instanceof MyPromise) {res.then(onFulfilledNext, onRejectedNext)
                        } else {onFulfilledNext(res)
                        }
                    }
                } catch (err) {onRejectedNext(err)
                }
            }
            switch (_status) {
                case PENDING:
                    this._fulfilledQueues.push(fulfilled)
                    this._rejectedQueues.push(rejected)
                    break
                case FULFILLED:
                    fulfilled(_value)
                    break
                case REJECTED:
                    rejected(_value)
                    break
            }
        })
    }
    // 增加 catch 办法
    catch(onRejected) {return this.then(undefined, onRejected)
    }
    // 增加动态 resolve 办法
    static resolve(value) {
        // 如果参数是 MyPromise 实例,间接返回这个实例
        if (value instanceof MyPromise) return value
        return new MyPromise(resolve => resolve(value))
    }
    // 增加动态 reject 办法
    static reject(value) {return new MyPromise((resolve, reject) => reject(value))
    }
    // 增加动态 all 办法
    static all(list) {return new MyPromise((resolve, reject) => {
            /**
             * 返回值的汇合
             */
            let values = []
            let count = 0
            for (let [i, p] of list.entries()) {
                // 数组参数如果不是 MyPromise 实例,先调用 MyPromise.resolve
                this.resolve(p).then(res => {values[i] = res
                    count++
                    // 所有状态都变成 fulfilled 时返回的 MyPromise 状态就变成 fulfilled
                    if (count === list.length) resolve(values)
                }, err => {
                    // 有一个被 rejected 时返回的 MyPromise 状态就变成 rejected
                    reject(err)
                })
            }
        })
    }
    // 增加动态 race 办法
    static race(list) {return new MyPromise((resolve, reject) => {for (let p of list) {
                // 只有有一个实例率先扭转状态,新的 MyPromise 的状态就跟着扭转
                this.resolve(p).then(res => {resolve(res)
                }, err => {reject(err)
                })
            }
        })
    }
    finally(cb) {
        return this.then(value => MyPromise.resolve(cb()).then(() => value),
            reason => MyPromise.resolve(cb()).then(() => { throw reason})
        );
    }
}

其它问题

1. Promise 与宏观工作、async 函数等执行程序问题

Promise 是微工作的一种实现,给出如下的代码,剖析其输入程序

// 题目 1
async function a1 () {console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {console.log('a2')
}
console.log('script start')
setTimeout(() => {console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {console.log('promise1')
})
a1()
let promise2 = new Promise((resolve) => {resolve('promise2.then')
    console.log('promise2')
})
promise2.then((res) => {console.log(res)
    Promise.resolve().then(() => {console.log('promise3')
    })
})
console.log('script end')
// 题目 2
async function async1() {console.log('async1 start');
    await async2();
    await async3()
    console.log('async1 end');
}
async function async2() {console.log('async2');
}
async function async3() {console.log('async3');
}
console.log('script start');
setTimeout(function() {console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {console.log('promise1');
    resolve();}).then(function() {console.log('promise2');
});
console.log('script end');
// 题目 3
async function async1(){console.log('async1 start')
    await async2()
    console.log('async1 end')
  }
async function async2(){console.log('async2')
}
console.log('script start')
setTimeout(function(){console.log('setTimeout0') 
},0)  
setTimeout(function(){console.log('setTimeout3') 
},3)  
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
async1();
new Promise(function(resolve){console.log('promise1')
    resolve();
    console.log('promise2')
}).then(function(){console.log('promise3')
})
console.log('script end')

答案:
题目一:script start->a1 start->a2->promise2->script end->promise1->a1 end->promise2.then->promise3->setTimeout
题目二:script start->async1 start->async2->promise1->script end->async3->promise2->async1 end->setTimeout
在浏览器 console 可试验
题目三:script start->async1 start->async2->promise1->promise2
->script end->nextTick->async1 end->promise3->setTimeout->setImmediate->setTimeout3
在 node 环境中可试验

2. 如何实现 all、如何实现链式调用的

all()的实现:函数中保护了一个数组和计数器,数组的大小为初始时 all 函数中传递的 Promise 对象数量,数组存储各个 Promise 执行胜利后 resolve 失去的后果,每胜利一个计数器 +1,直到计数器累加到数组大小时即调用 resolve(value),只有有一个 Promise 执行到失败的回调,即全副失败。
链式调用的实现:then 函数返回的仍然是一个 Promise, 见第二版的 Promise 实现。

3. all 中任何一个 Promise 出错,导致其它正确的数据也无奈应用,如何解决呢?

办法 1:应用 allSettled 代替;办法 2:改写 Promise, 将 reject 操作换成是 resolve(new Error(“ 自定义的谬误 ”));办法 3:引入第三方库 promise-transaction

4.Promise 真的好用吗,它存在什么问题?

问题 1:没有提供中途勾销的机制;问题 2:必须要设置回调,否则外部谬误无奈在内部反映进去;问题 3:应用时仍然存在大量 Promise 的 api, 逻辑不清晰。

退出移动版