Promise

错误之处,欢迎指正。


1. 简介

在学习Promise之前,需要搞清楚JavaScript的异步机制,了解事件循环机制以及事件队列。
Promise出现之前,我们都是使用回调函数的方式来进行异步操作,随着前端功能越来越复杂,就可能出现回调地狱(当前的异步操作需要等待之前的异步操作完成才能执行);回调也不能良好的处理有联系的异步操作,例如某个异步操作需要等待多个异步操作完成。
PromiseES6中提出的异步解决方案,本质上Promise是一个构造函数,通过new Promise()的形式来创建一个实例。

2. Promise

  1. 两个阶段
  • unsettled 未决阶段
  • settled 已决阶段
  1. 三种状态
  • pending 等待状态
  • resolved 成功状态
  • rejected 失败状态
  1. 两个过程
  • resolve过程
    pendingresolved
  • reject过程
    pendingrejected (代码执行错误和抛出错误,都会进入`rejected·状态)
  1. Promise基础用法
    Promise并没有消除回调,而是使用了特定的模式来让回调可控,从而解决回调地狱等问题。
const pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (Math.random() > 0.1) {
            resolve('没中奖');
        } else {
            resolve('中奖了');
        }
    }, 100);
})
pro.then(data => {
    console.log(data);
})
pro.catch(err => {
    console.log(err);
})

注意:在未决阶段的处理函数中,如果发生了未捕获的错误,会将状态推向rejected

  1. Promise串联
    通过串联这种方式,良好的解决了回调地狱的嵌套问题。
    这里要记住,Promise中,无论是then方法还是catch方法,返回值都是一个全新的Promise,因此才产生了Promise串联模式。
const pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('123')
    }, 1000);
})
const pro2 = pro.then(data => {
    console.log(data); //123
    return 456
})
pro2.then(data => {
    console.log(data); //456
})
console.log(pro2); //pending

如果pro.then中抛出了错误,那么pro2rejected状态;如果没有抛出错误,则把pro.then中的函数返回值作为resolved的传递值传给pro2

const pro1 = new Promise((resolve, reject) => {
    resolve(1);
})
const pro2 = new Promise((resolve, reject) => {
    resolve(2);
})
const pro3 = pro1.then(data => {
    return pro2;
})
pro3.then(data => {
    console.log(data); //2
})

如果Promise的处理函数返回的是一个Promise,那么新的Promise要和返回的Promise状态和数据保持一致。例如上述代码中,pro1的后续处理返回的是pro2,那么pro3就要和pro2的状态以及数据保持一致,因此返回了2

  1. finally函数
const pro = new Promise((resolve, reject) => {
    resolve(true)
})
pro.then(data => {
    console.log(data);
})
pro.finally(() => { //没有参数
    console.log('pro执行完毕');
})

Promise达到已决状态就会执行函数finally【ES2018】。

  1. resolvereject静态方法
const pro = Promise.resolve(1);
pro.then(data => {
    console.log(data); //1
})
const pro2 = Promise.reject('error');
pro2.catch(message => {
    console.log(message); //error
})

Promise还提供了两个静态方法resolvereject,这两个方法都会直接返回一个新的Promise
特殊情况:

const pro = new Promise(resolve => {
    resolve(1);
})
const pro2 = Promise.resolve(pro);
pro2.then(data => {
    console.log(data);  //1
})
console.log(pro === pro2); //true

如果Promise.resolve传递的参数是一个Promise,那么直接返回被传递的Promise。(谷歌浏览器)

  1. all
const pro1 = new Promise(resolve => {
    resolve(1);
})
const pro2 = new Promise((resolve, reject) => {
    resolve('error');
})
const arr = [pro1, pro2];
const newPro = Promise.all(arr);
newPro.then(data => {
    console.log(data);  //[1, 'error']
})

all方法接收一个由多个Promise组成的可迭代对象,返回一个新的Promise对象,只有当参数中的所有Promiseresolved状态时,该方法返回的新的Promise才会为resolved状态,只要有一个不是resolved状态,那么新的Promise就为rejected状态;resolved状态下,新的Promise的数据是参数中Promise数据组成的数组,rejected状态下,新的Promise的数据是参数中rejected状态的Promise数据。

  1. race
const pro1 = new Promise((resolve, reject) => {
    reject(1);
})
const pro2 = new Promise((resolve, reject) => {
    resolve('error');
})
const arr = [pro1, pro2];
const newPro = Promise.race(arr);
newPro.then(data => {
    console.log(data);  
}, message => {
    console.log(message); //1
})

race方法与all方法类似,不同的是,只要参数中有一个为已决状态状态,那么race返回的Promise就是已决状态,并且和参数中的状态保持一致。

3. 在setTimout里执行Promise

setTimeout在宏队列,relovereject在微队列,微队列先执行,宏队列后执行。

console.log(1);
setTimeout(() => {
    console.log(3)
}, 0);
console.log(2);

上述代码最终输出结果为123;因为JavaScript语言的执行机制是先执行同步代码,同步代码执行完毕执行异步代码。

console.log(1);
setTimeout(() => {
    const pro = new Promise((resolve, reject) => {
        console.log(3);
        resolve(4);
    }).then(data => {
        console.log(data);
    })
}, 1000);
console.log(2);

上述代码最终输出结果为1234;先输出1,然后定时器进入宏队列,然后输出2,此时开始执行异步代码,微队列为空,执行宏队列里面的定时器,定义pro是同步代码,输出3,然后resolve进入微队列,同步代码执行完毕,执行异步代码resolve,输出4

4. 手写Promise

  1. 涉及到的知识点:ClassSymbol、立即执行函数、高阶函数。
  2. 大体步骤
  • 首先要实现Promise的状态和数据的改变
  • 其次是推向已决状态的实现
  • 然后是串联实现(比较难的一部分)
  • 最后是实现Promise的静态方法
  1. 代码
/*
1. 初始化,pending状态以及值的设定
2. 执行pending状态的处理函数
3. 在构造器中定义resolve和reject函数
4. 捕获pending状态处理函数的异常,如果有错误,直接推向reject
5. 定义状态改变函数(将未决推向已决状态的函数)
6. then、catch处理函数定义,如果此时是未决状态,就把处理函数传递过来的参数存放至执行队列;如果是已决状态,就直接执行。
*/
const myPromise = (() => {
    const PENDING = 'pending',
        RESOLVED = "resolved",
        REJECTED = "rejected",
        PromiseValue = Symbol('PromiseValue'), 
        PromiseStatus = Symbol('PromiseStatus'),
        changeStatus = Symbol('changeStatus'), 
        handle = Symbol('handle'),
        thenables = Symbol('thenables'),
        catchables = Symbol('catchables'),
        linkPromise = Symbol('linkPromise');
    return class myPromise {
        /**
         * 
         * @param {*} executor pending状态下的处理函数 
         */
        constructor(executor) {
            this[PromiseStatus] = PENDING;  //初始为pending状态
            this[PromiseValue] = undefined; //此时没有数据
            this[thenables] = [];           //then执行队列
            this[catchables] = [];          //catch执行队列

            const resolve = data => {
                this[changeStatus](RESOLVED, data, this[thenables]);
            } //resolve函数
            const reject = message => {
                this[changeStatus](REJECTED, message, this[catchables]);
            } //reject函数

            try {
                executor(resolve, reject); //执行处理函数
            } catch (error) { //错误状态推向rejected
                reject(error)
            }
        }
        /**
         * 
         * @param {*} status 状态
         * @param {*} value 数据
         */
        [changeStatus](status, value, queue) {  //推向已决状态
            if (this[PromiseStatus] !== PENDING) {
                return; //如果不是未决状态,直接返回不做处理
            }
            this[PromiseStatus] = status;  //改变状态
            this[PromiseValue] = value;    //改变数据
            queue.forEach(handler => {
                handler(this[PromiseValue]);  //执行队列后续处理执行
            });
        }

        then(thenable, catchable) {  //后续处理
            return this[linkPromise](thenable, catchable);
        }
        catch (catchable) {   //后续处理
            return this[linkPromise](undefined, catchable);
        }
        [handle](status, handler, queue) {
            if (this[PromiseStatus] === status) {
                setTimeout(() => {
                    handler(this[PromiseValue]);
                }, 0);
            } else {
                queue.push(handler);
            }
        }
        [linkPromise](thenable, catchable) {  //串联的实现
            function executor(data, exec, resolve, reject) {
                try {
                    const result = exec(data);
                    if (result instanceof myPromise) {
                        result.then(data2 => {
                            resolve(data2);
                        }, err => {
                            reject(err);
                        })
                    } else {
                        resolve(result)
                    }
                } catch (error) {
                    reject(error)
                }
            }
            return new myPromise((resolve, reject) => {
                this[handle](RESOLVED, data => {
                    if (typeof thenable !== 'function') {
                        resolve(data);
                        return;
                    }
                    executor(data, thenable, resolve, reject)
                }, this[thenables])
                this[handle](REJECTED, message => {
                    if (typeof thenable !== 'function') {
                        reject(message);
                        return;
                    }
                    executor(message, catchable, resolve, reject)
                }, this[catchables])
            })
        }
        static resolve(data) {
            if (data instanceof myPromise) {
                return data;
            } else {
                return new myPromise((resolve) => {
                    resolve(data);
                })
            }
        }
        static reject(message) {
            return new myPromise((resolve, reject) => {
                reject(message);
            })
        }
        static all(pros) {
            return new myPromise((resolve, reject) => {
                const result = pros.map(p => {
                    const obj = {
                        result: undefined,
                        status: false
                    };
                    p.then(data => {
                        obj.result = data;
                        obj.status = true;
                        const unResolved = result.filter(r => !r.status);
                        if (unResolved.length === 0) {
                            resolve(result.map(r => r.result))
                        }
                    }, message => {
                        reject(message);
                    })
                    return obj;
                })
            })
        }
        static race(pros) {
            return new myPromise((resolve, reject) => {
                pros.forEach(p => {
                    p.then(data => {
                        resolve(data);
                    }, message => {
                        reject(message);
                    })
                })
            })
        }
    }
})(); //使用立即执行函数来避免命名重复

5. asyncawait

asyncawait是 [ES2016(ES7)] 新增的两个关键字。

  1. async用于简化Promise的创建,用于修饰函数,放在函数最开始的位置,被修饰函数的返回结果一定是Promise对象。
async function foo() {
    return 2;
}
//等效于
function foo() {
    return new Promise((resolve, reject) => {
        resolve(2);
    })
}
  1. await一定要放在async函数中。
async function foo2() {
    const result = await foo();
    console.log(result); //2
}
//等效于
function foo2() {
    return new Promise((resolve, reject) => {
        foo().then(data => {
            const result = data;
            console.log(result); //2
        })
    })
}
  1. 特殊情况(面试有几率考)
async function foo() {
    const result = await 1;
    console.log(result); //1
}
//等效于
function foo() {
    return new Promise((resolve, reject) => {
        Promise.resolve(1).then(data => {
            const result = data;
            console.log(result);
        })
        resolve();
    })
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容