Promise完全解读

xizugogo

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()函数有两个参数,都是可选的,如果参数不是函数将会被忽略
    // 同一个Promise可以多次注册then(),特性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,逻辑不清晰。

阅读 159

千里之行,始于足下

5 声望
0 粉丝
0 条评论
你知道吗?

千里之行,始于足下

5 声望
0 粉丝
宣传栏