第七章 Promise

148 阅读11分钟

Promise基础

Promise是一套专门处理异步场景的规范,它能避免回调地域的产生,使异步代码更加清晰、简洁、统一

Promise规范

Promise规范大致如下:

  1. 所有的异步场景,都可以看作是一个异步任务,并且异步任务应该表现为一个对象,称之为Promise对象

  2. 每个Promise对象,应该有三个状态、两个阶段

    状态:挂起状态(pending)、完成状态(fullfilled)、失败状态(rejected)

    阶段:未决阶段(unsettled)、已决阶段(settled)

    当Promise处于未决阶段时,它的状态只能为pending,当Promise处于已决阶段时,它的状态可能是fulfilled,也可能是rejected

    image.png

    注意:

  • Promise总是从未决阶段进入已决阶段,无法逆向变化
  • Promise一旦进入已决阶段,状态也将固定下来,之后无法改变
  1. 状态从pending转变为fulfilled,这种变化称为resolve

    状态从pending转变为rejected,这种变化称为reject

    resolve时可以携带一个相关数据,reject时可以携带一个失败原因

    image.png

  2. 可以对处于已决阶段的Promise对象进行下一步处理(称为后续处理)

    针对fulfilled状态的Promise对象的后续处理称为onFulfilled,针对reject状态的Promise对象的后续处理称为onRejected

    onFulfilled的处理过程中可以拿到resolve时携带的相关数据,onRejected的处理过程中可以拿到reject时携带的失败原因

    image.png

Promise API

创建一个Promise对象:

const pro = new Promise(()=>{});

console.log(pro);		// Promise {<pending>}

创建Promise对象时需要传入一个回调函数,该回调函数就描述了任务的具体过程

传递给Promise构造函数的回调是立即执行的(同步执行的)

new Promise(()=>{
    console.log(1);
});

console.log(2);

/*
	1
	2
*/

回调函数中可以接收两个函数参数:resolve和reject,当调用resolve时,处于pending状态的Promise对象就会进入fulfilled状态,当调用reject时,处于pending状态的Promise对象就会进入rejected状态

在调用resolve或reject时可以传入参数,而参数就是变化发生时所携带的信息

const pro = new Promise((resolve, reject)=>{
    if(...){
        resolve("相关数据");
    } else {
        reject("失败原因");
    }
});

console.log(pro);
// Promise {<fulfilled>: "相关数据"}
// Promise {<rejected>: "失败原因"}

每个Promise对象中都有一个then方法,then方法中可以传入两个函数参数,其中第一个函数参数就是Promise对象进入fulfilled状态时的后续处理onFulfilled,第二个参数就是Promise对象进入rejected状态时的后续处理onRejected

onFulfilled中拿到的相关数据就是调用resolve时传入的信息,onRejected中拿到的失败原因就是调用reject时传入的信息

const pro = new Promise((resolve, reject)=>{
    if(...){
        resolve("相关数据");
    }else {
        reject("失败原因");
    }
});

pro.then(
    (data)=>{					// onFulfilled
    	console.log(data);		// "相关数据"
    },
    (reason)=>{					// onRejected
        console.log(reason);	// "失败原因"
    }
);

注意:

  • 尽管在给Promise对象配置then方法之前,Promise对象的状态就已经变为了fulfilled了,但then方法中的onFulfilled仍然能够执行

  • 如果new Promise()中的回调抛出了错误,则该Promise对象会直接进入rejected状态,失败原因为错误对象

    注意:该回调中抛出的错误只会影响回调中的后续代码,不会影响到外部

    const pro = new Promise(()=>{
        throw new Error("error");
        console.log("abc");		// 不执行
    });
    
    // 正常执行
    console.log(pro);			// Promise {<rejected>: Error: error...}
    

Promise的链式调用

catch方法

catch方法中传入的回调就是onRejected

pro.catch(()=>{})

// 等效于
pro.then(null, ()=>{});

链式调用

then和catch方法都会返回一个新的Promise对象

const pro1 = new Promise(()=>{});

const pro2 = pro1.then();

console.log(pro2);		// Promise {<pending>}

then和catch方法返回的Promise对象,它的状态转变不同于直接new Promise()创建的Promise对象

then和catch方法返回的Promise对象的状态转变规则如下:

  1. 若上一个Promise对象还处于未决阶段,则then或catch返回的新Promise对象的状态和上一个Promise对象一样,也为pending

  2. 若上一个Promise对象的进入了已决阶段,但then或catch中没有对应的进一步处理,则then或catch返回的新Promise对象的状态和数据最终会与上一个Promise对象的保持一致

    const pro1 = new Promise((resolve)=>{
        resolve("pro1完成后的数据");	// pro1的状态变为fulfilled
    });
    
    const pro2 = pro1.then();		// then中没有onFulfilled
    
    console.log(pro2);				// Promise {<pending>}
    
    setTimeout(()=>{
        console.log(pro2);			// Promise {<fulfilled>: "pro1完成后的数据"}
        console.log(pro1 === pro2);	// false
        // 注意:只是状态和数据与上一个一致,并不是新Promise对象就是上一个Promise对象
    }, 0);
    
  3. 若上一个Promise对象的进入了已决阶段,且then或catch中有对应的后续处理,则根据后续处理的执行情况确定新Promise的状态和数据

    ① 后续处理执行无错,则新Promise的状态变为fulfilled,数据为后续处理的返回结果

    ② 后续处理执行出错,则新Promise的状态变为rejected,数据为抛出的错误对象

    ③ 后续处理中返回的是一个Promise对象,则新Promise对象的状态和数据与返回的Promise一致(但两者并不是同一个Promise)

注意:

  • then或catch中的onFulfilled和onRejected是被放入到微队列中,它们是异步执行的

  • 如果一个Promise的状态受另一个Promise状态的影响,则由于另一个Promise的状态有可能是异步改变的,因此该Promise的状态一定是异步改变的

finally方法

finally中的回调既不是onFulfilled,也不是onRejected

只要上一个Promise已决,finally中的回调就会运行

finally中的回调在运行时,既拿不到上一个Promise完成后的相关数据,也拿不到上一个Promise失败后的相关原因

若finally中的回调执行无错,且回调的返回值不是Promise,则finally返回的Promise的状态和数据与上一个Promise保持一致

若finally中的回调执行无错,但回调的返回值是Promise:若回调返回的Promise状态为fulfilled,则finally返回的Promise状态和数据与上一个Promise保持一致;否则finally返回的Promise的状态和数据会与回调返回的Promise保持一致

若finally中的回调执行有错,则其返回的Promise的状态会变为rejected,失败原因为抛出的错误

const pro1 = new Promise((resolve, reject) => {
    resolve(1);
});

const pro2 = pro1.finally(() => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);
        }, 0);
    });
});

const pro3 = pro1.finally(() => {
    throw 3;
});

const pro4 = pro1.finally(() => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(4);
        }, 0);
    });
});

setTimeout(() => {
    console.log(pro2);				// Promise {<fulfilled> 1}
    console.log(pro3);				// Promise {<rejected> 3}
    console.log(pro4);              // Promise {<rejected> 4}
}, 100);

Promise的静态方法

  • Promise.resolve(data)

    若data不是一个Promise,则相当于:

    new Promise((resolve)=>{
        resolve(data);
    });
    

    若data是一个Promise,则直接返回data

    const pro1 = Promise.reject(1);
    
    const pro2 = Promise.resolve(pro1);
    
    console.log(pro2 === pro1);			// true
    

    若data像一个Promise(即data并不是ES6标准的Promise,但满足Promise规范),则返回的Promise的状态与数据与data保持一致

  • Promise.reject(reason)

    直接返回一个rejected状态的Promise

    它等同于:

    new Promise((resolve, reject)=>{
        reject(reason);
    });
    
  • Promise.all(Promise集合)

    Promise集合本质上是一个可迭代对象

    返回一个Promise,当传入的Promise集合全部fulfilled时该Promise将转变为fulfilled,若集合中出现一个rejected则该Promise将转变为rejected,若集合中没有rejected且存在pending则该Promise的状态为pending

    若返回的Promise状态为fulfilled,则其数据为Promise集合中所有Promise的数据组成的数组

    若返回的Promise状态为rejected,则其数据为Promise集合中第一个rejected的Promise的数据一致

    若Promise集合中有不是Promise的元素,则内部会自动使用Promise.resolve()对其包裹

    若传入的Promise集合是一个空集合,则all直接返回一个相关数据为空数组的成功Promise

  • Promise.any(Promise集合)

    Promise集合本质上是一个可迭代对象

    返回一个Promise,当传入的Promise集合出现一个fulfilled时该Promise将转变为fulfilled,若全部rejected则该Promise将转变为rejected,若集合中没有fulfilled且存在pending则该Promise的状态为pending

    若返回的Promise状态为fulfilled,则其数据为Promise集合中第一个fulfilled的Promise的数据一致

  • Promise.allSettled(Promise集合)

    Promise集合本质上是一个可迭代对象

    返回一个Promise,当传入的Promise集合全部已决时该Promise将转变为fullfilled,否则为pending

    const pro = Promise.allSettled([
        Promise.resolve(1),
        new Promise((resolve)=>{
    		resolve(2);
        }, 1000);
        Promise.reject(3)
    ]);
    
    setTimeout(()=>{
        console.log(pro);
    }, 1000);
    

    image.png

  • Promise.race(Promise集合)

    返回一个Promise,该Promise的状态和数据会与Promise集合中第一个已决的Promise保持一致

以上的静态方法,若返回的是数组,则数组中元素的相对顺序会与集合中对应Promise的相对顺序保持一致,并不会因某个先已决就排在前面

async和await

async

async用于修饰函数,被它修饰的函数,一定返回Promise

被async修饰的函数,它原本的返回值,会成为真正返回的Promise完成后的相关数据

async function method(){
	return 1;
}

console.log(method());		// Promise {<fulfilled>: 1}

注意:

  • 若没有手动return,则函数默认为return undefined

  • 只有async函数执行完return语句后,其返回的Promise的状态才能变为fulfilled

    async function method(){}
    
    console.log(method());		// Promise {<fulfilled>: undefined}
    

若被async修饰的函数中抛出了错误,则该函数返回的Promise的状态为rejected,失败原因即抛出的错误

async function method(){
    throw new Error("error");
    console.log(1);				// 不会执行
}

console.log(method());			// Promise {<rejected>: Error: error}

console.log(2);					// 正常执行

注意:async修饰的函数中抛出的错误只会影响函数中处于错误之后的代码,不会影响到外部

若被async修饰的函数原本返回的就是Promise,则该函数真正返回的Promise的状态和数据最终会与其保持一致(但两者并不相同)

const pro1 = Promise.resolve(1);	// Promise {<fulfilled>: 1}

async function method(){
    return pro1;
}

const pro2 = method();

console.log(pro2);					// Promise {<pending>}

setTimeout(()=>{
    console.log(pro2);				// Promise {<fulfilled>: 1}
    console.log(pro1 === pro2);		// false
}, 0);

await

await用于等待Promise完成,await必须使用在被async修饰的函数中

当被await等待的Promise完成后,await表达式将返回该Promise完成后的相关数据

(async ()=>{
    const data = await Promise.resolve(1);
    console.log(data);			// 1
})();

如果await等待的不是Promise,则await会将其视为Promise,并且原本的数据就是该Promise完成后的相关数据

async function test(){
    const data = await "abc";		// 相当于是await Promise.resolve("abc");
    console.log(data);
}

test();			// "abc"

如果await等来的是一个rejected状态的Promise,则该await表达式将会抛出异常

async function test(){
    await Promise.reject(1);	// 将抛出异常
}

const pro = test();

console.log(pro);			// Promise {<pending>}

setTimeout(()=>{
    console.log(pro);		// Promise {<rejected>: 1}
}, 0);

可以配合try catch捕获async函数中await语句所抛出的错误

注意:

  • await关键字必须直接位于async修饰的函数中

    async function test(){
        (()=>{
            await Promise.resolve(1);		// 抛出SyntaxError
        })();
    }
    
  • 只要async函数在没执行结束时执行到了await,则同步访问该函数返回的Promise时,其状态一定为pending

    async function test(){
        await ...;
    }
    
    const pro = test();
    
    console.log(pro);			// Promise {<pending>}
    
  • await等待的是Promise,而不是等待它后面的表达式,而所谓的await表达式是指await以及它身后的具体Promise

    async function test(){
        const data = await new Promise((resolve)=>{
            console.log(1);
            resolve(2);
        });
        console.log(data);
    }
    
    test();
    console.log(3);
    
    // 此函数中的await表达式是指【await Promise {<fulfilled>: 1}】,而不是指【await new Promise((resolve)=>{resolve(1);});】
    
    /**
    	1
    	3
    	2
    */
    
  • await表达式之后的代码,相当于放到了onFulfilled之中,当await正常等待结束时,它们就会被放入到微队列里

    async function test(){
        setTimeout(()=>{
            console.log(1);
        }, 0);
        const data = await Promise.resolve(2);
        console.log(data);
    }
    
    test();
    console.log(3);
    
    /**
    	3
    	2
    	1
    */
    
    async function test() {
        setTimeout(() => {
            console.log(1);
        }, 0);
        const data = await new Promise((resolve) => {
            setTimeout(() => {
                resolve(2);
            }, 0);
        });
        console.log(data);
    }
    
    test();
    
    console.log(3);
    
    /**
    	3
    	1
    	2
    */
    

手写Promise

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function enAsyncQueue(callback) {
    // globalThis指代全局对象,浏览器环境为window,node环境为global
    if (globalThis.process && globalThis.process.nextTick) {		// 判断node环境
        process.nextTick(callback);
    } else if(globalThis.MutationObserver) {
        const p = document.createElement("p");
        const observer = new MutationObserver(callback);
        observer.observe(p, {
            childList: true
        });
        p.innerHTML = "1";
    } else {
        setTimtout(callback, 0);
    }
}

function isPromise(obj) {
    // 判断obj是否满足Promise A+规范
    // Promise A+规范规定Promise必须是一个对象,且对象中必须包含一个名为then的属性,属性值为一个函数
    return !!(obj && typeof (obj) === "object" && typeof (obj.then) === "function");
}

class MyPromise {
    constructor(executor) {
        this._status = PENDING;
        this._value = undefined;
        this._handlers = [];
        try {
            executor(this._resolve.bind(this), this._reject.bind(this));
        } catch (err) {
            this._reject(err);
            console.error(err);
        }
    }

    _changeStatus(status, value) {
        if (this._status === PENDING) {
            this._status = status;
            this._value = value;
            this._runHandlers();
        }
    }
    
    _resolve(data) {
        this._changeStatus(FULFILLED, data);
    }

    _reject(err) {
        this._changeStatus(REJECTED, err);
    }

    _runHandlers() {
        if (this._status !== PENDING) {
            while (this._handlers.length) {
                this._runOneHandler(this._handlers[0]);
                this._handlers.shift();
            }
        }
    }

    _runOneHandler({ handler, status, resolve, reject }) {
        if(this._status !== status){
            return;
        }
        enAsyncQueue(() => {
            if (typeof (handler) !== "function") {
                this._status === FULFILLED ? resolve(this._value) : reject(this._value);
                return;
            }
            try {
                const result = handler(this._value);
                if (isPromise(result)) {
                    result.then(resolve, reject);
                } else {
                    resolve(result);
                }
            } catch (err) {
                reject(err);
                console.error(err);
            }
        });
    }
    
    _pushHandler(handler, status, resolve, reject) {
        this._handlers.push({
            handler,
            status,
            resolve,
            reject
        });
    }

    then(onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            this._pushHandler(onFulfilled, FULFILLED, resolve, reject);
            this._pushHandler(onRejected, REJECTED, resolve, reject);
            this._runHandlers();
        });
    }

    catch(onRejected) {
        return this.then(null, onRejected);
    }

    finally(onSettled) {
        return this.then((data) => {
            return MyPromise.resolve(onSettled()).then(() => {
                return data;
            });
        }, (err) => {
            return MyPromise.resolve(onSettled()).then(() => {
                throw err;
            })
        });
    }

    static resolve(data) {
        if (data instanceof Promise) {
            // data如果是ES6的Promise,则直接返回
            return data;
        }
        return new MyPromise((resolve, reject) => {
            if (isPromise(data)) {
                // data如果像Promise,则返回的Promise状态和数据与其一致
                data.then(resolve, reject);
            } else {
                resolve(data);
            }
        });
    }

    static reject(err) {
        return new MyPromise((resolve, reject) => {
            reject(err);
        });
    }

    static all(proms) {									// proms应该是一个迭代器
        const results = [];
        let count = 0;
        let fulfilledCount = 0;
        return new MyPromise((resolve, reject) => {
            try {										// 如果传入的proms不是一个迭代器
                for (const prom of proms) {
                    const index = count;                // 记录pro在proms中的位置
                    count++;
                    // 使用MyPromise.resolve()包裹pro是为了防止pro不是Promise
                    MyPromise.resolve(prom).then((data) => {
                        results[index] = data;
                        fulfilledCount++;
                        if (fulfilledCount === count) {
                            resolve(results);
                        }
                    }, reject);
                }
                if (count === 0) {
                    // 传入的proms是一个空集合
                    resolve([]);
                }
            } catch (err) {
                // 传入的proms不是迭代器
                reject(err);
                console.error(err);
            }
        });
    }

    static any(proms) {
        const errors = [];
        let count = 0;
        let rejectCount = 0;
        return new MyPromise((resolve, reject) => {
            try {
                for (const prom of proms) {
                    const index = count;
                    MyPromise.resolve(prom).then(resolve, (err) => {
                        rejectCount++;
                        errors[index] = err;
                        if (rejectCount === count) {
                            reject(new AggregateError(errors, "All promises were rejected"));
                        }
                    });
                    count++;
                }
                if (count === 0) {
                    reject(new AggregateError(errors, "All promises were rejected"));
                }
            } catch(err){
                reject(err);
                console.error(err);
            }
        });
    }

    static allSettled(proms) {
        const promArr = [];
        for (const prom of proms) {
            promArr.push(MyPromise.resolve(prom).then((data) => {
                return {
                    status: FULFILLED,
                    value: data,
                }
            }, (err) => {
                return {
                    status: REJECTED,
                    reason: err
                }
            }));
        }
        return MyPromise.all(promArr);
    }

    static race(proms){
        return new MyPromise((resolve, reject) => {
            for (const prom of proms) {
                MyPromise.resolve(prom).then(resolve, reject);
            }
        });
    }
}