javascript-Promise

loading 2022年11月12日 232次浏览

1.含义

Promise是异步编程的一种解决方案,在es6中它被写进语言标准,统一了用法,原生提供了Promise对象。

通俗地说,Promise像一个容器,保存着某个未来结束的事件(异步事件)的结果。

而从语法上说,Promise是一个对象,可以从中获得异步操作的信息,通过各种API对异步方法进行处理。

总之,有了Promise对象,就可以将异步操作以同步操作的流程表达出来。

异步方法不会立即返回最终的值,而是先返回一个promise,以便未来把值交给使用者。

2.基本特点

Promise对象具有以下两个特点:

(1)对象的状态不受外界影响
Promise对象代表一个异步操作,有三种状态

  • pending 初始状态
  • fulfilled 操作成功
  • rejected 操作失败

只有异步操作的结果,可以决定当前的状态。 任何其它操作都无法改变这个状态。

(2)状态一旦改变后,就不会再次发生改变
状态改变只有两种可能:

  • pending -> fullfilled
  • pending -> rejected

改变发生后,状态就凝固了,此时的状态称为:

  • resolved

如果状态已经成为resolved,即使再对Promise对象添加回调函数,也会立即得到这个结果。

平时习惯用resolved状态指fullfilled状态,rejected状态仍然由rejected表示。

Promise也有一些缺点:

(1)一旦新建就会立即执行,无法中途取消

(2)如果不设置回调函数,Promise内部throw的错误不会反应到外部

(3)处于pending状态时,无法得知目前进展

3.基本用法

3.1 构造

es6中,Promise对象是一个构造函数,可以用来生成实例。

Promise构造函数以一个函数作为参数这个函数的两个参数又是两个函数,分别是resolve和reject

这两个函数由js提供,不用自己部署。

        const promise = new Promise((resolve ,reject) =>{
            //...some code

            if(/*异步操作成功*/){
                resolve(value);
            }else{
                reject(error);
            }
        })

一般使用resolve()方法指代状态从pending变成fullfilled,在异步操作成功时调用;

用reject()方法指代状态变为rejected,在异步操作失败时调用。

就像mdn中提到的:

We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.

比如这个例子,如果图片加载成功,就调用resolve方法,否则调用reject方法:

        function loadImageAsync(url) {
            return new Promise(function (resolve, reject) {
                const image = new Image();

                image.onload = function () {
                    resolve(image);
                };

                image.onerror = function () {
                    reject(new Error('Could not load image at ' + url));
                };

                image.src = url;
            });
        }

3.2 then/catch

promise实例生成后,可以通过then方法接受两个函数作为参数,分别指定resolved状态和rejected状态的回调函数。这两个函数都接受实例对象传出的值作为参数。

        promise.then((value) =>{
            //success
        } , (error) =>{
            //failure
        })

例如这个基本的例子,体现出resolve()的传参和then方法内函数如何接收传过来的参数。

        let myFirstPromise = new Promise((resolve, reject) => {
            setTimeout(() =>{
                //在异步事件执行成功时将resolve内参数传给then方法作为参数
                resolve("Success!")
            }, 250)
        })

        myFirstPromise.then((successMessage) => {
            console.log("Yay! " + successMessage) //Yay! Success!
        });

3.2.1 promise实例作为resolve()参数

通常来说,reject()的参数是Error对象的实例,也就是

reject(new Error(...))

而对于resolve()来说参数是很多样的,甚至可以将另一个Promise实例作为参数。即一个异步操作的结果是返回另一个异步操作。 例如下面这个例子:

        const p1 = new Promise((resolve , reject) =>{
            setTimeout(() =>{
                // resolve('congrats , you success') , 3000
                reject(new Error('sorry,you fail'))
            } , 3000)
        })
        const p2 = new Promise((resolve , reject) =>{
            setTimeout(() =>{
                resolve(p1)
            } , 1000)
        })
        p2.then(result => console.log(result))
        .catch(error => console.log(error));

(1)若p1为pending,则p2需要等p1执行完才能更新自己状态

例子中p2状态在1s后就已经改变,然而resolve传入p1,因此p2自己的状态失效,由p1决定。同时p2后面的then语句也由p1决定。因此再过了2s后,p1变为rejected,触发catch方法指定的回调函数。

(2)若p1已经resolved或者rejected,那么p2会立刻执行

那么如果p2后触发呢,将p2中的setTimeout时间修改为

        const p2 = new Promise((resolve , reject) =>{
            setTimeout(() =>{
                resolve(p1)
            } , 6000)
        })

那么3s一到就会变成p1先改变状态,但是由于p1没有相应的catch(),会导致3s-6s时捕获不到错误。

然后到6s时p2状态改变,立即执行catch方法,输出捕获到的报错信息:

3.2.2 then的链式调用

比如第一个then方法的回调函数返回一个Promise对象,这时第二个then方法指定的回调函数就要等待这个Promise对象状态变化来决定自己是调用resolve对应的回调函数还是rejected对应的回调函数。

        getJSON("/post/1.json")
            .then(
                post => getJSON(post.commentURL)
            ).then(
                comments => console.log("resolved:", comments),
            ).catch(
                err => console.log("rejected:" , err)
            )

3.2.3 catch指定回调函数捕获错误

catch的用法上面已经学过了,注意一下不同的写法就行

        const promise = new Promise((resolve , reject) =>{
            throw new Error('test');
            //等效于reject(new Error('test'))
        })
        .catch(error => console.log(error))

如果Promise状态已经变成resolved,那么再抛出错误是无效的。

        const promise = new Promise((resolve , reject) =>{
            resolve('ok')
            reject(new Error('test'))
        })
        .then(msg => console.log(msg))
        .catch(error => console.log(error))

然而即使没有catch,promise内部的错误也不会影响外部代码的执行

        const someAsyncThing = function () {
            return new Promise(function (resolve, reject) {
                // 下面一行会报错,因为x没有声明
                resolve(x + 2);
            });
        }
        someAsyncThing().then(() => console.log('resolved'))

        console.log('123')
	//123
	// Uncaught (in promise) ReferenceError: x is not defined

同样,catch也是可以链式调用的

        someAsyncThing().then(()=>{
            return someOtherAsyncThing();
        }).catch(error=> {
            console.log('oh no', error);
            // 下面一行会报错,因为y没有声明
            y + 2;
        }).catch(error =>{
            console.log('carry on', error);
        });

4.常用API

4.1 Promise.prototype.finally()

4.1.1 用法

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况

比如这个例子,不管发生了什么,都会打印'finally'

        let p1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                //resolve('success')
                reject(new Error('fail'))
            }, 1000)
        })
            .finally(() => {
                console.log('finally')
            })
            .then(
                msg => console.log(msg),
                err => {throw err}
            )
	//finally
	//Uncaught (in promise) Error: fail

4.1.2 手写

其实finally就是把本来要写的then和catch合起来了而已,相当于不管怎样都执行callback(),重写的逻辑是比较清晰的:

  • 无论如何执行callback()
  • 用Promise.resolve()将callback()执行结果转化为Promise对象,这样才能加上then方法,将接收到的msg/err继续传到下一层then中
        Promise.prototype.myFinally = function(callback){
            return this.then(val =>{
	        // 把上层传过来的val return出来传下去
                return Promise.resolve(callback()).then(() => val);
            } , err =>{
		// 把上层传过来的err throw出来传下去
                return Promise.resolve(callback()).then(() => {throw err})
            })
        }

简洁版

        Promise.prototype.myFinally = function(callback) {
            return this.then(
                val => Promise.resolve(callback()).then(() => val),
                err => Promise.resolve(callback()).then(() => {throw err})
            )
        }

此处为什么要在callback()后面return一个值而不是这么写只执行callback()就完事呢

msg => Promise.resolve(callback()),
err => Promise.resolve(callback())

比如3.1.1中的例子,finally()下方还有个then方法接收finally传递下来的参数然后打印,因此在callback()后面加上then方法用来把finally从上面接收到的resolve/reject传过来的参数继续往下传。

4.1.3 返回值

finally方法总是会返回原来的值

// resolved 的值是 undefined
console.log(Promise.resolve(2).then(() => {}, () => {}))

// resolved 的值是 2
console.log(Promise.resolve(2).finally(() => {}))

// rejected 的值是 undefined
console.log(Promise.reject(3).then(() => {}, () => {}))

// rejected 的值是 3
console.log(Promise.reject(3).finally(() => {}))

4.2 Promise.resolve()

4.2.1 用法

Promise.resolve()可以将现有对象转为Promise对象

        let p = Promise.resolve(123);
        console.log(p); //Promise {<fulfilled>: 123}       
        p.then(val => console.log(val)); //123

Promise.resolve()等价于上文中的构造Promise后在其中写resolve()

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

4.2.2 四种参数

Promise.resolve()方法的参数分成四种情况。

(1) 参数本身就是一个Promise实例

原封不动地返回这个实例

(2) 参数是一个具有then方法的对象

比如这个例子

        let thenableObj = {
            then : (resolve , reject) =>{
                resolve(123);
            }
        }

        let p = Promise.resolve(thenableObj);
        
        p.then(val => console.log(val)); //123

Promise.resolve()会将这个对象转化为Promise对象,然后执行对象中的then方法。

(3) 参数是普通对象/不是对象

如果参数是原始类型或者是不带then方法的普通对象,那么Promise.resolve()返回一个新的Promise对象,状态为resolved

        let p = Promise.resolve('yoimiya');

        p.then(val => console.log(val)); //yoimiya

(4) 参数为空

会直接返回一个resolved状态的Promise对象

        let p = Promise.resolve();

        p.then(val => console.log(val)); //undefined

resolve()操作在本轮事件循环结束时执行,而非在下一轮事件循环开始时

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

       Promise.resolve().then(() => console.log(2));
       
       console.log(1);
       //1
       //2
       //3

4.3 Promise.reject()

4.3.1 用法

类似于resolve(),Promise.reject()也是返回一个新的Promise()实例,不过实例的状态为rejected。

	const p = Promise.reject('出错了');
	// 等同于
	const p = new Promise((resolve, reject) => reject('出错了'))

        p.then(null , err =>{
            console.log(err) //sorry
        })

传参到then/catch里的方法也是一样的

        Promise.reject('sorry')
        .then(null , e =>{
            console.log(e); //sorry
        })
	//等同于
        Promise.reject('sorry')
        .catch(err =>{
            console.log(err) //sorry
        })

4.4 Promise.all()

4.4.1 用法

Promise.all()方法将多个Promise实例包装成一个新的Promise实例。

const p = Promise.all([p1 , p2 , p3]);

Promise.all()接受一个iterable类型(Array,Map,Set等)作为参数,iterable类型中每个对象都是Promise实例(若不是,就先调用Promise.resolve()转换)。

        const p1 = Promise.resolve(1);
        const p2 = 2;
        const p3 = new Promise((resolve , reject) =>{
            setTimeout(()=>{
                resolve(3)
            } , 1000)
        })
        Promise.all([p1 , p2 , p3]).then(val =>{
            console.log(val) //Array(3):1 2 3
        })

数组里也可以不是Promise实例,可以是常量,不过就会被忽略掉,但是仍然会被放在返回数组中。

        //相当于传了个空数组 fulfilled 将数组作为参数传递
        const p1 = Promise.all([1,2,3]);

        //相当于只传了Promise.resolve(3) fulfilled 将数组作为参数传递
        const p2 = Promise.all([1,2,Promise.resolve(3)]);

        //相当于只传了Promise.reject(3) 将reject()里的3作为参数传递
        const p3 = Promise.all([1,2,Promise.reject(3)]);

        setTimeout(()=>{
            console.log(p1); //Promise {<fulfilled>: Array(3)}
            console.log(p2); //Promise {<fulfilled>: Array(3)}
            console.log(p3); //Promise {<rejected>: 3} Uncaught (in promise) 3
        })

4.4.2 特点

Promise.all()构造的实例的状态由参数内部Promise实例状态决定:

const p = Promise.all([p1 , p2 , p3]);

(1) p1,p2,p3的状态都为fullfilled,p的状态才会变成fulfilled,此时p1,p2,p3的返回值组成一个数组,传递给p的回调函数。(就如同4.4.1中的例子)

(2) 只要p1,p2,p3中有一个rejected,p的状态就会变成rejected,此时第一个被reject的实例的返回值,会被传递给p的回调函数。

当其中一个实例被执行失败且被catch时,剩下的实例也是会执行的,因为Promise在实例化时就执行完了,then()只是用来执行接下来的回调函数。

        const p1 = Promise.resolve(1);
        const p2 = 2;
        const p3 = new Promise((resolve , reject) =>{
            setTimeout(()=>{
                reject('error')
            } , 1000)
        })
        Promise.all([p1 , p2 , p3])
        .then(val =>{
            console.log(val)
        })
        .catch(err =>{
            console.log(err) //error
        })

如果作为参数的Promise实例自己定义了catch方法,那么当它被rejected不会触发Promise.all()的catch方法。

注意此时all()里的then方法也有输出,因为错误已经被捕获了,p3执行完catch方法后也会变成resolved,因此p1,p2,p3还是均为resolved,调用then方法内的回调函数。

        const p1 = Promise.resolve(1);
        const p2 = 2;
        const p3 = new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('error')
            }, 1000)
        })
            .catch(err => {
                console.log(`p3自己就把${err}给捕获了`);
                return 'err'
            })
        Promise.all([p1, p2, p3])
            .then(val => {
                console.log(val) // [1 , 2 , 'err']
            })
            .catch(err => {
                console.log(err)
            })

4.4.3 手写

具体见注释即可,理解后较为清晰:

但是要先注意一个事件循环的小问题,我们知道那道常规的for循环打印问题是由于setTimeout也属于宏任务代码要轮到下一个循环中才能执行导致的,但是似乎微任务在for循环中也要轮到最后来处理,说明for循环要执行完毕才算完成一轮宏任务?

        let cnt = 0;
        for(const item of [1,2,3,4,5]){
            cnt++;          
            Promise.resolve(123)
                .then(val => console.log(val))
            console.log(cnt);
        }
        // 1 2 3 4 5
        // 123*5

因此再来看all()的手写,留意到通过一个变量length来记录输入参数arr的长度,也是利用了上面这个原理:

        const myPromiseAll = function (arr) {
            return new Promise((resolve, reject) => {
                const promiseRes = [];
                // 用于遍历arr
                let i = 0;
                // 用于保存arr长度
                let length = 0;
                // 已完成的数量 用于判断啥时候返回
                let fulCnt = 0;

                //用for...of迭代iterator数据(因为传入的可能是array/set/map等)
                for (const item of arr) {
                    // 计算并保存容器长度 注意这里是宏任务 能实现保存效果
                    i++;
                    length = i;
                    //包一层 兼容参数非Promise的情况 宏任务for执行完后才走这里的微任务
                    Promise.resolve(item)
                        .then(val => {
                            i--;
                            promiseRes[i] = val;
                            fulCnt++;
                            //完成的数量等于容器长度 返回
                            if (fulCnt === length) {
                                resolve(promiseRes);
                            }
                        })
                        .catch(err => {
                            reject(err);
                        })
                }

		// 如果传入的是空数组
		if(length === 0) resolve([]);
            })
        }

一开始看着复杂,其实自己了解了事件循环那块后是很清晰的。

4.5 Promise.race()

4.5.1 用法和特点

大部分都和Promise.all()差不多 就不浪费过多篇幅

const p = Promise.race([p1, p2, p3]);

最大的特点是:

只要p1/p2/p3中有一个实例率先改变状态,p的状态就随之改变。率先改变的Promise实例的返回值就作为参数传递给p的回调函数。

        const promise1 = new Promise((resolve, reject) => {
            setTimeout(resolve, 500, 'one');
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(resolve, 100, 'two');
        });

        Promise.race([promise1, promise2]).then((value) => {
            console.log(value); //two
        });

参数也是可以传基础类型的,会直接返回靠前的基础类型值。

4.5.2 手写

        const myPromiseRace = function(args){
            return new Promise((resolve , reject) =>{
                for(const item of args){
                    //出现第一个promise成功或失败时就返回结果
                    Promise.resolve(item).then(resolve , reject);
                }
            })
        }

其中

        Promise.resolve(item).then(resolve, reject);
	//相当于
	Promise.resolve(item).then(val => resolve(val) , err => reject(err));

有一个地方不是特别清晰,就是for循环中怎么判断promise完成然后调用then()方法,以下是测试代码:

        const promise1 = new Promise((resolve, reject) => {
            setTimeout(resolve, 1500, 'onasdasde');
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(resolve, 100, 'two');
        });

        let arr = [promise1 , promise2];
        for(const item of arr){
            console.log('out' , item);
            item.then(() => {
                console.log('in' , item)
            })
        }

很显然处于for循环宏任务中时两个promise都还是pending状态,但是微任务中能等到规定时间后自动调用then()方法,原理感觉有点懂又不完全懂,先这么记着。

4.6 Promise.allSettled()

4.6.1 用法

Promise.allSettled()方法当参数中所有的promise都已经有了结果(fulfilled/rejected)后,返回一个对象数组。表示对应的结果。

也就是说不像all()方法,rejected后会立刻返回,allSettled()一定是所有Promise实例都有结果了后才返回

注意allSettled()的then()的回调函数是接收一个数组,该数组的元素对应传入allSettled()数组里面的两个Promise对象。

        const promise1 = Promise.resolve(1);
        const promise2 = new Promise((resolve , reject) =>{
            setTimeout(reject , 1000 , 2);
        })
        Promise.allSettled([promise1 , promise2])
            .then(
                items => items.forEach(item => console.log(item))
                //{status: 'fulfilled', value: 1}
                //{status: 'rejected', reason: 2}
            )

下面给出一个体现返回值的用法的例子

        const promises = [fetch('index.html'), fetch('https://does-not-exist/')];
        const results = await Promise.allSettled(promises);

        // 过滤出成功的请求
        const successfulPromises = results.filter(p => p.status === 'fulfilled');

        // 过滤出失败的请求,并输出原因
        const errors = results
            .filter(p => p.status === 'rejected')
            .map(p => p.reason);

4.6.2 手写

(1)改造Promise.all()以简便实现
allSettled()和all()最大区别就是一个遇到reject()继续执行,一个遇到reject()就返回。

一层层分析:

  1. 首先返回的是一个Promise.all()的结果,也就是一个Promise实例,用来做第12行的then()处理。

  2. args是传进来的[p1,p2]这个实例数组,通过args.map(...)返回一个新的数组作为Promise.all()的新参数。Promise.all()会根据新参数进行返回处理。

  3. then中有两种返回值,如果item的状态为resolved,比如p1,那么then方法就触发第一个回调函数,返回{status:fulfilled , value: res}这个对象作为第二步中新的数组的元素,反之亦然。

  4. 但是由于then返回的对象是自定义的对象而非rejected状态的promise实例,因此all()会将其作为resolved看待返回一整个数组。

通过all()返回的实例和其中的新数组

        const myAllSettled = function(args){
            return Promise.all(args.map(item => Promise.resolve(item).then(res =>{
                return {status: 'fulfilled' , value: res};
            } , err =>{
                return {status: 'rejected' , reason: err}
            })))
        }

        const p1 = Promise.resolve(1);
        const p2 = Promise.reject(2);
        myAllSettled([p1 , p2])
            .then(
                results => results.forEach(res => console.log(res))
            )

(2) 原始实现
和all()是类似的,搞得懂那个这个道理也差不多

        const myPromiseAllSettled = function(arr){
            return new Promise((resolve , reject) =>{
                const res = [];
                let i = 0;
                let length = 0;
                let fulCnt = 0;

                for(const item of arr){
                    i++;
                    length = i;
                    Promise.resolve(item)
                        .then(val =>{
                            res[--i] = {status: 'fulfilled' , value: val};
                            fulCnt++;
                            if(fulCnt === length){
                                resolve(res);
                            }
                        })
                        .catch(err =>{
                            res[--i] = {status: 'rejected' , reason: err};
                            fulCnt++;
                            if(fulCnt === length){
                                resolve(res);
                            }
                        })
                }

		// 如果传入的是空数组
		if(length === 0) resolve([]);
            })
        }

4.7 Promise.any()

4.7.1 用法

Promise.any()方法接收一组Promise实例作为参数,返回一个新的Promise实例。

只要参数实例中有一个实例变成fulfilled,返回实例就变成fulfilled

只有所有参数都变成rejected,返回实例才变成rejected。(可以发现逻辑和all()相反)

Promise.any()抛出的错误是一个AggregateError实例,这个实例对象的errors属性是一个数组,包含了所有成员的错误。

var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);

Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
  console.log(result); // 42
});

Promise.any([rejected, alsoRejected]).catch(function (results) {
  console.log(results instanceof AggregateError); // true
  console.log(results.errors); // [-1, Infinity]
});

4.7.2 手写

其实any()的手写和all()的手写就一个地方相反,别的地方均相同:

all()是成功数满了才resolve(),有一个失败就reject()

any()是有一个成功就reslove(),失败满了才reject()

因此代码也是很类似的

        const myPromiseAllSettled = function(arr){
            return new Promise((resolve , reject) =>{
                const res = [];
                let i = 0;
                let length = 0;
                let fulCnt = 0;

                for(const item of arr){
                    i++;
                    length = i;
                    Promise.resolve(item)
                        .then(val =>{
                            resolve(val)
                        })
                        .catch(err =>{
                            res[--i] = {status: 'rejected' , reason: err};
                            fulCnt++;
                            if(fulCnt === length){
                                resolve(res);
                            }
                        })
                }

		// 如果传入的是空数组
		if(length === 0) resolve([]);
            })
        }

5.应用

5.1. 应用举例

应用举例

5.2 交替亮灯

现要求实现3s亮红灯,1s亮绿灯,2s亮黄灯交替重复实现,这是亮灯函数:

        function red() {
            console.log('red');
        }
        function green() {
            console.log('green');
        }
        function yellow() {
            console.log('yellow');
        }

5.2.1 Promise写法

通过Promise的链式调用和递归实现

        let task = (timer, light) => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    if (light === 'red') {
                        red()
                    }
                    if (light === 'green') {
                        green()
                    }
                    if (light === 'yellow') {
                        yellow()
                    }
                    resolve()
                }, timer);
            })
        }

        let execute = () => {
            task(3000, 'red')
                .then(() => task(1000, 'green'))
                .then(() => task(2000, 'yellow'))
                .then(execute)
        }
        execute()

这里有两个小问题需要注意:

  1. 两种写法的区别?
.then(() => task(1000, 'green'))

.then(task(1000, 'green'))

因为只有上面那种写法才能正确使得当promise实例fulfilled后才执行task,下面那种写法会直接执行task()

  1. 那么为什么要写成.then(execute)?
.then(execute)

.then(() => execute())

这两者实际上是等效的,因为execute没有接受参数,其实和上面Promise.race()的手写那里的简介写法是一个道理。

所以这里千万不能这么写,否则就犯了第一点的错误:

.then(execute())

5.2.2 async/await写法

通过async/await实现

        let execute = async () => {
            await task(3000, 'red')
            await task(1000, 'green')
            await task(2000, 'yellow')
            execute()
        }
        execute()

6.一些看代码写输出特殊题目

Promise.resolve()
  .then(() => {
    return new Error("error!!!");
  })
  .then(res => {
    console.log("then:", res);
  })
  .catch(err => {
    console.log("catch:", err);
  });

可能第一反应是走catch,实际上是输出是then: Error: error!!!

注意then中是return一个错误,而非throw一个错误(这种情况下才走catch)。

Promise.reject('err!!!')
  .then(
    (res) => { console.log('success:', res); },
    (err) => { console.log('error:', err); }
  )
  .catch(err => { console.log('catch:', err); });

输出是error: err!!!,then的第二个参数捕获了,catch就不会捕获了,除非是这种情况,在then的捕获中再throw一个error,才会触发catch:

Promise.reject('error!!!')
  .then(
    (res) => { console.log('success:', res); },
    (err) => { throw new Error('new error in error callback'); }
  )
  .catch(err => { console.log('catch:', err); });
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)

输出是1,要注意then中只有传入函数才有效,因此第二行和第三行都是无效的,第一行的1透传到最后的console.log。

Promise.resolve("1")
  .then(res => {
    console.log(res);
  })
  .finally(() => {
    console.log('finally');
  });

Promise.resolve('2')
  .finally(() => {
    console.log('finally2');
    return '我是finally2返回的值';
  })
  .then(res => {
    console.log('finally2后面的then函数:', res);
  });

输出是:

1
finally2
finally
finally2后面的then函数: 2

微任务队列变化过程如下:
将p1中的then推入
将p2中的finally2推入
执行并移出p1的then 推入p1中的finally
执行并移出p2的finally2 推入p2中的then
执行并移出p1的finally
执行并移出p2中的then

async function async1() {
  console.log("async1 start");
  await new Promise(resolve => {
    console.log('promise1');
  })
  console.log('async1 success');
  return 'async1 end';
}

console.log('script start');
async1().then(res => console.log(res));
console.log('script end');

输出是:
script start
async1 start
promise1
script end

因为await后面的Promise没有被resolve,因此下方的async1 success和async1 end都不会输出。

那么假如加上resolve呢?

async function async1() {
  console.log("async1 start");
  await new Promise(resolve => {
    console.log('promise1');
    resolve('promise1 resolve');
  }).then(res => console.log(res));
  console.log('async1 success');
  return 'async1 end';
}

console.log('script start');
async1().then(res => console.log(res));
console.log('script end');

输出是:
script start
async1 start
promise1
script end
promise1 resolve
async1 success
async1 end

为什么async1 end能被打印出来 难道async1中不应该把return 'async1 end'改成resolve('async1 end')才能让then起作用吗?

别忘了async会将返回值包装成Promise对象,这样就相当于resolve了。

  1. 知识点集合
const async1 = async () => {
  console.log("async1");
  setTimeout(() => {
    console.log('timer1');
  }, 2000);
  await new Promise(resolve => {
    console.log('promise1');
    resolve();
  });
  console.log('async1 end');
  return "async1 success";
};

console.log('script start');
async1().then(res => console.log(res));
console.log('script end');

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res));

setTimeout(() => {
  console.log('timer2');
}, 1000);

输出是:
script start
async1
promise1
script end
async1 end
async1 success
1
timer2
timer1

用到的知识点有这些:

  • then必须传函数,否则无效。
  • setTimeout不管入队的先后,时间短的先执行

7. 手写简易Promise

这里要用两个队列来存走到then的时候,状态还是pending(比如resolve被SetTimeout包裹)的情况。

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.fulfilledQueue = [];
    this.rejectedQueue = [];

    const resolve = value =>{
      if (this.state = 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.fulfilledQueue.forEach(cb => cb(this.value));
      }
    }

    const reject = reason =>{
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.rejectedQueue.forEach(cb => cb(this.reason));
      }
    }

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    } else if (this.state === 'rejected') {
      onRejected(this.reason);
    } else if (this.state = 'pending') {
      this.fulfilledQueue.push(onFulfilled);
      this.rejectedQueue.push(onRejected);
    }
  }
}

测试代码

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('Success!');
    // reject('Error!')
  }, 1000);
});

promise.then(
  (value) => {
    console.log('Resolved with value:', value);
  },
  (reason) => {
    console.error('Rejected with reason:', reason);
  }
);