为什么要使用Promise ? Promise A+规范 | 使用及手写一个promise

本文详细探讨了Promise的用途,解释了为何使用Promise来解决回调地狱问题,并介绍了宏任务和微任务的概念及其执行顺序。通过一个具体的示例展示了Promise执行流程。同时,文章详细阐述了PromiseA+规范,包括Promise的状态转换、then方法的特性以及微任务的执行。最后,给出了PromiseA+的简单实现,并解答了一个关于Promise输出顺序的疑惑。
摘要由CSDN通过智能技术生成

1.为什么要用Promise

为了解决过长的链式回调函数造成的回调地狱,使其层次结构更加清晰明朗!

//常见的回调地狱实例
const loadImg = (url,callback) => {
    setTimeout(()=>{
        console.log('url是:'+url);
        callback();
    },500)
} 
loadImg('localhost/img1',()=>{
    loadImg('localhost/img2',()=>{
        loadImg('localhost/img3',()=>{
            console.log('all done');
        })
    })
})

代码层次结构很乱,不便于维护

2.Promise的出现

提到promise,不得不说一个名词叫做异步任务,异步任务又分为宏任务和微任务。

其中:微任务的出现是为了在宏任务的执行中,碰到需要紧急处理的情况,而把优先级提前。

宏任务有

  1. setTimeout
  2. setInterval
  3. setImmediate
  4. ajax

微任务有

  1. process.nextTick
  2. Promise.then catch finally

执行顺序为:先宏再微:先宏任务,再清理宏任务中的微任务队列,再进行下一个宏任务队列内的宏任务,遵循先进先出原则。

举个例子(可以先看第三点promise a+规范,以便更好理解):

const p = function () {
    return new Promise((resolve, reject) => {
        const p1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(1)
            }, 0)
            resolve(2)
        })
        p1.then((res) => {
            console.log(res);
        })
        console.log(3);
        resolve(4);
    })
}


p().then((res) => {
    console.log(res);
})
console.log('end');

1.第一遍扫描:从上往下看,碰到p(),再看p方法里面,碰到p1,在看p1里面,有一个setTimeout宏任务,放入宏任务队列,碰到resolve(2),则将p1.then()方法放入微任务队列。再往下碰到console.log(3),则第一个输出为3

3

再往下看,碰到resolve(4),将p().then()方法放入微任务队列。再往下碰到console.log(‘end’),则输出end

end

2.清空微任务队列,此时微任务队列内的任务为[p1.then(),p().then()],先看p1.then(),console.log(res),res为2,则输出2

2

再看p().then(),console.log(res),res为4,则输出4

4

3.开启下一个宏任务扫描,下一个宏任务是setTimeout,往下看,碰到resolve(1),此时要注意,因为当前setTimeout的resolve已经在上一步清空为任务队列的第一个任务中已经被resolve了,所以此时的resolve(1)已经没有任何作用了,所以不输出任何东西,而不是一般想象中的输出1

4.没有任何任务了,结束。

所以最终结果输出

3

end

2

4

3.Promise A+规范

## 术语

  1. promise 是一个有then方法的对象或者是函数,行为遵循本规范

  2. thenable 是一个有then方法的对象或者是函数

  3. value 是promise状态成功时的值,也就是resolve的参数, 包括各种数据类型, 也包括undefined/thenable或者是 promise

  4. reason 是promise状态失败时的值, 也就是reject的参数, 表示拒绝的原因

  5. exception 是一个使用throw抛出的异常值

## 规范

接下来分几部分来讲解PromiseA+规范.

### Promise States

promise应该有三种状态. 要注意他们之间的流转关系.

  1. pending

1.1 初始的状态, 可改变.

1.2 一个promise在resolve或者reject前都处于这个状态。

1.3 可以通过 resolve -> fulfilled 状态;

1.4 可以通过 reject -> rejected 状态;

  1. fulfilled

2.1 最终态, 不可变.

2.2 一个promise被resolve后会变成这个状态.

2.3 必须拥有一个value值

  1. rejected

3.1 最终态, 不可变.

3.2 一个promise被reject后会变成这个状态

3.3 必须拥有一个reason

Tips: 总结一下, 就是promise的状态流转是这样的

pending -> resolve(value) -> fulfilled

pending -> reject(reason) -> rejected

### then

promise应该提供一个then方法, 用来访问最终的结果, 无论是value还是reason.

  1. 参数要求

1.1 onFulfilled 必须是函数类型, 如果不是函数, 应该被忽略.

1.2 onRejected 必须是函数类型, 如果不是函数, 应该被忽略.

  1. onFulfilled 特性

2.1 在promise变成 fulfilled 时,应该调用 onFulfilled, 参数是value

2.2 在promise变成 fulfilled 之前, 不应该被调用.

2.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)

  1. onRejected 特性

3.1 在promise变成 rejected 时,应该调用 onRejected, 参数是reason

3.2 在promise变成 rejected 之前, 不应该被调用.

3.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)

  1. onFulfilled 和 onRejected 应该是微任务

这里用queueMicrotask来实现微任务的调用.

  1. then方法可以被调用多次

5.1 promise状态变成 fulfilled 后,所有的 onFulfilled 回调都需要按照then的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onFulfilled的回调)

5.2 promise状态变成 rejected 后,所有的 onRejected 回调都需要按照then的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onRejected的回调)

  1. 返回值

then 应该返回一个promise

```js

promise2 = promise1.then(onFulfilled, onRejected);

```

6.1 onFulfilled 或 onRejected 执行的结果为x, 调用 resolvePromise( 这里大家可能难以理解, 可以先保留疑问, 下面详细讲一下resolvePromise是什么东西 )

6.2 如果 onFulfilled 或者 onRejected 执行时抛出异常e, promise2需要被reject

6.3 如果 onFulfilled 不是一个函数, promise2 以promise1的value 触发fulfilled

6.4 如果 onRejected 不是一个函数, promise2 以promise1的reason 触发rejected

  1. resolvePromise

```js

resolvePromise(promise2, x, resolve, reject)

```

7.1 如果 promise2 和 x 相等,那么 reject TypeError

7.2 如果 x 是一个 promsie

​ 如果x是pending态,那么promise必须要在pending,直到 x 变成 fulfilled or rejected.

​ 如果 x 被 fulfilled, fulfill promise with the same value.

​ 如果 x 被 rejected, reject promise with the same reason.

7.3 如果 x 是一个 object 或者 是一个 function

​ let then = x.then.

​ 如果 x.then 这步出错,那么 reject promise with e as the reason.

​ 如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise)

​ resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject);

​ rejectPromise 的 入参是 r, reject promise with r.

​ 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。

​ 如果调用then抛出异常e

​ 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略

​ 则,reject promise with e as the reason

​ 如果 then 不是一个function. fulfill promise with x.

4.常见的promise的使用

const get = url => { 
    return new Promise(r => {
        $.get(url, data => {
            r(data)
        });
    })
};
get('localhost/img1')
    .then(imgUrl2=>get(imgUrl2))
	.then(imgUrl3=>get(imgUrl3))

因为axios API放回的也是一个promise对象,所以也适用于promise规范

import axios from 'axios';
axios.get('localhost/img1')
	.then(imgUrl2=>axios.get(imgUrl2))
	.then(imgUrl3=>axios.get(imgUrl3))

5.手写一个promise A+

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

class MPromise {

    FULFILLED_CALLBACK_LIST = [];
    REJECTED_CALLBACK_LIST = [];
    _status = PENDING;

    constructor(fn) {
        // 初始状态为pending
        this.status = PENDING;
        this.value = null;
        this.reason = null;

        try {
            fn(this.resolve.bind(this), this.reject.bind(this));
        } catch (e) {
            this.reject(e);
        }
    }

    get status() {
        return this._status;
    }

    set status(newStatus) {
        this._status = newStatus;
        switch (newStatus) {
            case FULFILLED: {
                this.FULFILLED_CALLBACK_LIST.forEach(callback => {
                    callback(this.value);
                });
                break;
            }
            case REJECTED: {
                this.REJECTED_CALLBACK_LIST.forEach(callback => {
                    callback(this.reason);
                });
                break;
            }
        }
    }

    resolve(value) {
        if (this.status === PENDING) {
            this.value = value;
            this.status = FULFILLED;
        }
    }

    reject(reason) {
        if (this.status === PENDING) {
            this.reason = reason;
            this.status = REJECTED;
        }
    }

    then(onFulfilled, onRejected) {
        const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
            return value;
        }
        const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) => {
            throw reason;
        };

        const fulFilledFnWithCatch = (resolve, reject, newPromise) => {
            queueMicrotask(() => {
                try {
                    if (!this.isFunction(onFulfilled)) {
                        resolve(this.value);
                    } else {
                        const x = fulFilledFn(this.value);
                        this.resolvePromise(newPromise, x, resolve, reject);
                    }
                } catch (e) {
                    reject(e)
                }
            })
        };

        const rejectedFnWithCatch = (resolve, reject, newPromise) => {
            queueMicrotask(() => {
                try {
                    if (!this.isFunction(onRejected)) {
                        reject(this.reason);
                    } else {
                        const x = rejectedFn(this.reason);
                        this.resolvePromise(newPromise, x, resolve, reject);
                    }
                } catch (e) {
                    reject(e);
                }
            })
        }

        switch (this.status) {
            case FULFILLED: {
                const newPromise = new MPromise((resolve, reject) => fulFilledFnWithCatch(resolve, reject, newPromise));
                return newPromise;
            }
            case REJECTED: {
                const newPromise = new MPromise((resolve, reject) => rejectedFnWithCatch(resolve, reject, newPromise));
                return newPromise;
            }
            case PENDING: {
                const newPromise = new MPromise((resolve, reject) => {
                    this.FULFILLED_CALLBACK_LIST.push(() => fulFilledFnWithCatch(resolve, reject, newPromise));
                    this.REJECTED_CALLBACK_LIST.push(() => rejectedFnWithCatch(resolve, reject, newPromise));
                });
                return newPromise;
            }
        }
    }

    resolvePromise(newPromise, x, resolve, reject) {
        // 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
        // 这是为了防止死循环
        if (newPromise === x) {
            return reject(new TypeError('The promise and the return value are the same'));
        }

        if (x instanceof MPromise) {
            // 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
            // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
            x.then((y) => {
                resolvePromise(newPromise, y, resolve, reject);
            }, reject);
        } else if (typeof x === 'object' || this.isFunction(x)) {
            // 如果 x 为对象或者函数
            if (x === null) {
                // null也会被判断为对象
                return resolve(x);
            }

            let then = null;

            try {
                // 把 x.then 赋值给 then 
                then = x.then;
            } catch (error) {
                // 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
                return reject(error);
            }

            // 如果 then 是函数
            if (this.isFunction(then)) {
                let called = false;
                // 将 x 作为函数的作用域 this 调用
                // 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
                try {
                    then.call(
                        x,
                        // 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
                        (y) => {
                            // 需要有一个变量called来保证只调用一次.
                            if (called) return;
                            called = true;
                            this.resolvePromise(promise, y, resolve, reject);
                        },
                        // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                        (r) => {
                            if (called) return;
                            called = true;
                            reject(r);
                        });
                } catch (error) {
                    // 如果调用 then 方法抛出了异常 e:
                    if (called) return;

                    // 否则以 e 为据因拒绝 promise
                    reject(error);
                }
            } else {
                // 如果 then 不是函数,以 x 为参数执行 promise
                resolve(x);
            }
        } else {
            // 如果 x 不为对象或者函数,以 x 为参数执行 promise
            resolve(x);
        }
    }

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

    isFunction(param) {
        return typeof param === 'function';
    }

    static resolve(value) {
        if (value instanceof MPromise) {
            return value;
        }

        return new MPromise((resolve) => {
            resolve(value);
        });
    }

    static reject(reason) {
        return new MPromise((resolve, reject) => {
            reject(reason);
        });
    }

    static race(promiseList) {
        return new MPromise((resolve, reject) => {
            const length = promiseList.length;

            if (length === 0) {
                return resolve();
            } else {
                for (let i = 0; i < length; i++) {
                    MPromise.resolve(promiseList[i]).then(
                        (value) => {
                            return resolve(value);
                        },
                        (reason) => {
                            return reject(reason);
                        });
                }
            }
        });
    }

    static all(promiseList){
        return new MPromise((resolve,reject) => {
            const finish = promiseFinish(promiseList,resolve);
            promiseList.forEach((promise,index)=>{
                promise.then(value=>{
                    finish(value,index);
                })
            })
        })
    }

    promiseFinish(promiseList,resolve) {
        const valueFinished = [];
        return function(value,index) {
            valueFinished[index] = value;
            if(valueFinished.length === promiseList){
                resolve(valueFinished);
            }
        }
    }
}

补充:一种别的promiseA+书写方法
链接:手写promiseA+
主要处理下底下这种情况
// const promise = new MPromise((resolve,reject)=>{
// resolve(1111);
// })

// promise
// .then(res=>{console.log(res);return ‘2222’})
// .then((res)=>{console.log(res,‘xixixi’)});
// promise
// .then(console.log)

6.疑题答问

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() =>{
    console.log(6);
})

1.首先思考了一下宏任务与微任务,还有promise,模拟了一下输出结果

0

1

4

2

3

5

6

实际上promise的输出结果

0

1

2

3

4

5

6

根据自写的promise和其它手写的promise的输出结果

0

1

2

4

3

5

6
为什么会有差异呢?

在console.log(0)下面一行的时候,return Promise.resolve(4),如果resolvePromise内的resolve(value)的value是一个promise对象的时候,会依次占用两个微任务队列。但是根据自写的promise的这里来看,仅仅只会占用一个微任务队列。

            // 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
            // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
            x.then((y) => {
                resolvePromise(newPromise, y, resolve, reject);
            }, reject);

但是手写的MPromise也是符合promise A+规范的一个实例,各方面请教后才知道有一句话,当执行return promise的时候需要等到当前执行栈为空的时候才会return 掉新的promise,所以才会在微任务队列中占用了两个微任务,但其实手写的promise和原生promise都是符合promise A+,所以接下来也就不细究了。

Promise是一种异步编程的解决方案,可以避免回调地狱的问题。下面是使用class手写一个Promise的示例代码: ``` class MyPromise { constructor(executor) { this.status = 'pending'; // Promise的状态 this.value = undefined; // Promise的结果 this.reason = undefined; // Promise的错误信息 const resolve = (value) => { if (this.status === 'pending') { this.status = 'fulfilled'; this.value = value; } }; const reject = (reason) => { if (this.status === 'pending') { this.status = 'rejected'; this.reason = reason; } }; try { executor(resolve, reject); } catch (error) { reject(error); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value; onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason }; const promise2 = new MyPromise((resolve, reject) => { if (this.status === 'fulfilled') { setTimeout(() => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, ); } else if (this.status === 'rejected') { setTimeout(() => { try { const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, ); } else { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, ); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }, ); }); } }); return promise2; } catch(onRejected) { return this.then(null, onRejected); } } function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { reject(new TypeError('Chaining cycle detected for promise')); } let called = false; if (x !== null && (typeof x === 'object' || typeof x === 'function')) { try { const then = x.then; if (typeof then === 'function') { then.call(x, (y) => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, (r) => { if (called) return; called = true; reject(r); }); } else { resolve(x); } } catch (error) { if (called) return; called = true; reject(error); } } else { resolve(x); } } ``` 以上是一个简单的Promise实现,其中包含了Promise的基本功能,如状态管理、then方法、catch方法等。需要注意的是,Promise的实现并不是一件简单的事情,需要考虑到各种边界情况,才能保证其正确性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值