Promise知识点记录

169 阅读6分钟

1. 宏任务与微任务

1.1 什么是宏任务与微任务

我们都知道 Js 是单线程的,但是一些高耗时操作就带来了进程阻塞问题。为了解决这个问题,Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)

在异步模式下,创建异步任务主要分为宏任务与微任务两种。ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。

1.2 宏任务与微任务的几种创建方式

image-20220329111020400

1.3 如何理解script(整体代码块)是个宏任务

实际上如果同时存在两个 script 代码块,会首先在执行第一个 script 代码块中的同步代码,如果这个过程中创建了微任务并进入了微任务队列,第一个 script 同步代码执行完之后,会首先去清空微任务队列,再去开启第二个 script 代码块的执行。所以这里应该就可以理解 script(整体代码块)为什么会是宏任务。

1.4 什么是EventLoop

先来看个图

EventLoop.png

  1. 判断宏任务队列是否为空

    • 不空 --> 执行最早进入队列的任务 --> 执行下一步
    • 空 --> 执行下一步
  2. 判断微任务队列是否为空

    • 不空 --> 执行最早进入队列的任务 --> 继续检查微任务队列空不空

    • 空 --> 执行下一步

因为首次执行宏队列中会有 script(整体代码块)任务,所以实际上就是 Js 解析完成后,在异步任务中,会先执行完所有的微任务,这里也是很多面试题喜欢考察的。需要注意的是,新创建的微任务会立即进入微任务队列排队执行,不需要等待下一次轮回。

image-20220329111441628

2.Promise概述

2.1 Promise的状态

  1. 实例对象中的一个属性 『PromiseState』

    • pending 未决定的

    • resolved / fullfilled 成功

    • rejected 失败

  2. promise的状态改变

    • pending变为resolved/fullfilled

    • pending变为rejected

  • 只有这两种变化可能,也就是说不可能由resolved变为rejected,且一个promise对象只能改变一次
  • 无论变为成功还是失败,都会有一个结果数据
  • 成功的结果数据一般称为value,失败的结果数据一般称为reason

2.2 Promise 对象的值

实例对象中的另一个属性 『PromiseResult』 保存着异步任务『成功/失败』的结果,下面两个函数可以修改对返回的结果进行修改,其他皆不可以

  • resolve()
  • reject ()

2.3Promise 的基本流程

img

3. Promise的使用

3.1 API

  1. Promise构造函数:Promise(executor){}
    • executor函数:执行器(resolve,reject) => {}
      • resolve函数:内部定义成功时我们调用的函数 value => {}
      • reject函数:内部定义失败时我们调用的函数 reason => {}
  • 说明:executor会在Promise内部立即同步调用,异步操作在执行器中执行
<script>
        let p = new Promise((resolve,reject)=>{
            //同步调用
            console.log(111);
        });
        console.log(222);
</script>

image-20220329113137882

  1. Promise.prototype.then方法:(onResolved,onReject) => {}

    • onResolved函数:成功的回调函数 (value) => {}

    • onReject函数:失败的回调函数 (reason) => {}

  • 说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调返回一个新的promise对象
  1. Promise.prototype.catch方法:(onRejected) => {}
    • onRejected函数:失败的回调函数 (reason) =>{}
  • 说明:then()的语法糖,相当于:then(undefined,onRejected)
 <script>
        let p = new Promise((resolve,reject)=>{
           //修改promise对象的状态
           reject('error')
        });
        //执行catch方法
        p.catch(reason =>{
            console.log(reason); //输出error
            
        })
    </script>
  1. Promise.resolve方法:(value) => {}
    • value:成功的数据或promise对象
  • 说明:返回一个成功/失败的 promise 对象
<script>
        let p1 = Promise.resolve(123);
        //如果传入的参数为非Promise类型的对象,则返回的结果皆为成功的promise对象
        console.log('p1:',p1);

        //如果传入的参数为Promise对象,则参数的结果决定resolve的结果
        let p2 = Promise.resolve(new Promise((resolve,reject) => {
            //成功的结果
            resolve('OK')
        }))
        console.log("p2:",p2);
       
        let p3 = Promise.resolve(new Promise((resolve,reject) => {
            //失败的结果
           reject('error');
        }))
        console.log("p3:",p3);
    </script>

image-20220329132207867

  1. Promise.reject 方法: (reason) => {}
    • reason:失败的原因或promise对象

说明: 返回一个失败的 promise ==对象==

<script>
        let p1 = Promise.reject(456);
        //如果传入的参数为非Promise类型的对象,则返回的结果皆为成功的promise对象
        console.log('p1:',p1);

        //如果传入的参数为Promise对象,则参数的结果决定resolve的结果
        let p2 = Promise.reject(new Promise((resolve,reject) => {
            resolve('OK')
        }))
        //即使传入的时一个成功的promise对象,返回的也是失败的promise对象
        console.log("p2:",p2);//结果PromiseResult是传入的参数OK
       
        let p3 = Promise.reject(new Promise((resolve,reject) => {
            //失败的结果
           reject('error');
        }))
        console.log("p3:",p3);
    </script>

image-20220329132425621

  1. Promise.all方法:(promises) =>{}
    • promises:包含n个promise的数值
  • 说明:返回一个新的promise,只有所有的promise都成功了才成功,只要有一个失败了就直接失败
<script>
        let p1 = new Promise((resolve, reject) => {
            resolve('OK');
        })
        let p2 = Promise.resolve('Success');
        let p3 = Promise.resolve('Oh Yeah');
        let p4 = Promise.reject('Error');//失败的
        
        //成功的情况
        const resultSuccess = Promise.all([p1, p2, p3]);
        console.log("resultSuccess:",resultSuccess);

        //失败的情况
        const resultError = Promise.all([p1, p2, p4]);
        console.log("resultError:",resultError);
    </script>

img

  1. Promise.race方法:(promises) =>{}
    • promises:包含n个promise的数组
  • 说明:返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态
<script>
        let p1 = new Promise((resolve, reject) => {
                resolve('OK');
        })
        let p2 = Promise.resolve('Success');
        let p3 = Promise.resolve('Oh Yeah');

        let p4 = new Promise((resolve, reject) => { //添加定时器,异步任务
            setTimeout(() => {
                resolve('OK');
            }, 1000);
        })
        let p5 = Promise.reject('Error'); //失败的结果
        //调用
        const result1 = Promise.race([p1, p2, p3]);
        console.log("result1",result1);

        const result2 = Promise.race([p4, p2, p3]);
        console.log("result2",result2);

        const result3 = Promise.race([p4, p5,p1]);
        console.log("result3",result3);

        const result4 = Promise.race([p5, p1, p2]);
        console.log("result4",result4);
    </script>

img

3.2 Promise 的几个关键问题

  1. 如何改变promise的状态

    • resolve(value):如果当前是pending就会变为resolved

    • reject(reason): 如果当前是 pending 就会变为 rejected

    • 抛出异常: 如果当前是 pending 就会变为 rejected

  2. 一个 promise 指定多个成功/失败回调函数, 都会调用吗?

    • 当 promise 改变为对应状态时都会调用
 <script>
        let p = new Promise((resolve, reject) => {
             resolve('OK');//改变状态
        });

        ///指定回调 - 1
        p.then(value => {
            console.log(value);
        });

        //指定回调 - 2
        p.then(value => {
            alert(value);
        });
    </script>
  1. 改变 promise 状态和指定回调函数谁先谁后?

(1)都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再==指定==回调

<script>
        //先改变状态,再指定回调
        let p1 = new Promise((resolve, reject) => {
                resolve('OK');//同步任务
        });

        p1.then(value => {
            console.log(value);
        },reason=>{
            
        })

        //先指定回调,再改变状态
        let p2 = new Promise((resolve, reject) => {
            //异步任务
            setTimeout(() => {
                resolve('OK');
            },3000)
                
        });
    
        //then先执行,而不是then里面的回调函数先执行,所以value会再3s后输出
        p2.then(value => {
            console.log(value);//状态改变才能拿到数据
        },reason=>{
            
        })

    </script>

(2)如何先改状态再指定回调?

  • ① 在执行器中直接调用 resolve()/reject()
  • ② 延迟更长时间才调用 then()

(3) 什么时候才能得到数据?

  • 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
  • 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
  1. promise.then()返回的新 promise 的结果状态由什么决定?

(1)简单表达: 由 then()指定的回调函数执行的结果决定

(2)详细表达:

  • 如果抛出异常,新promise变为rejected,reason为抛出的异常
  • 如果返回的是非promise的任意值,新promise变为resolved,value为返回值
  • 如果返回的是另一个新的promise,此promise的结果就会成为新promise的结果
<script>
        let p = new Promise((resolve, reject) => {
            resolve('ok');
        });
        //执行 then 方法
        let result = p.then(value => {
            // console.log(value);
            //1. 抛出错误
            // throw '出了问题';
            //2. 返回结果是非 Promise 类型的对象
            // return 123;
            //3. 返回结果是 Promise 对象
            return new Promise((resolve, reject) => {
                resolve('success');
                // reject('error');
            });
        }, reason => {
            console.warn(reason);
        });

        console.log(result);
    </script>
  1. promise 如何串连多个操作任务?

    • promise 的 then()返回一个新的 promise, 可以看成 then()的链式调用

    • 通过 then 的链式调用串连多个同步/异步任务

<script>
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK');
            }, 1000);
        });

        p.then(value => {
            return new Promise((resolve, reject) => {
                resolve("success");
            });
        }).then(value => {
            console.log(value); //success
        }).then(value => {
            console.log(value); 
            //undefined,因为第二个then没有return,所以return undefined,所以
            //第二个then的返回结果是一个成功的promise且成功的结果是undefined,而不是返回一个promise对象
           //所以第三个then会输出第二个then返回的结果
        })
    </script>
  1. promise 异常传透?
    • 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,前面任何操作出了异常, 都会传到最后失败的回调中处理
 <script>
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK');
                // reject('Err');
            }, 1000);
        });

        p.then(value => {
            // console.log(111);
            throw '失败啦!';
        }).then(value => {
            console.log(222);
        }).then(value => {
            console.log(333);
        }).catch(reason => { //catch方法最后对错误进行统一的处理
            console.warn(reason);
        });
    </script>
  1. 中断 promise 链

    • 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数

    • 办法: 在回调函数中返回一个 pendding 状态的 promise 对象(有且只有一个方式)

<script>
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK');
            }, 1000);
        });

        p.then(value => {
            console.log(111);
            //有且只有一个方式
            return new Promise(() => {}); //返回一个pending状态的promise对象,状态不改变,后面的回调不再执行
        }).then(value => {
            console.log(222); 
        }).then(value => {
            console.log(333);
        }).catch(reason => {
            console.warn(reason);
        });
    </script>

4. async与await

4.1 async函数

  1. 函数的返回值为 promise 对象
  2. promise 对象的结果由 async 函数执行的返回值决定
<script>
        //then
        async function main(){
            //1. 如果返回值是一个非Promise类型的数据
            // return 521;
            //2. 如果返回的是一个Promise对象
            // return new Promise((resolve, reject) => {
            //     // resolve('OK');
            //     reject('Error');
            // });
            //3. 抛出异常
            throw "Oh NO";
        }

        let result = main();

        console.log(result);
 </script>

4.2 awai表达式

  1. await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
  2. 如果表达式是 promise 对象, await 返回的是 promise 成功的值
  3. 如果表达式是其它值, 直接将此值作为 await 的返回值
<script>
        async function main(){
            let p = new Promise((resolve, reject) => {
                // resolve('OK');
                reject('Error');
            })
            //1. 右侧为promise的情况,await 返回的是 promise 成功的值
            // let res = await p;
            // console.log(res);
            
            //2. 右侧为其他类型的数据,直接将此值作为 await 的返回值
            let res2 = await 20;
            console.log(res2);
            
            //3. 如果promise是失败的状态,用try catch进行捕获
            try{
                let res3 = await p;
            }catch(e){
                console.log(e); 
            }
        }

        main();
    </script>

4.3 注意

  1. await 必须写在 async 函数中, 但 async 函数中可以没有 await
  2. 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理

4.4 async与await结合使用

/**
 * resource  1.html  2.html 3.html 文件内容
 */

const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);

// //回调函数的方式
// fs.readFile('./resource/1.html', (err, data1) => {
//     if(err) throw err;
//     fs.readFile('./resource/2.html', (err, data2) => {
//         if(err) throw err;
//         fs.readFile('./resource/3.html', (err, data3) => {
//             if(err) throw err;
//             console.log(data1 + data2 + data3);
//         });
//     });
// });

//async 与 await
async function main(){
    try{
        //读取第一个文件的内容
        let data1 = await mineReadFile('./resource/1.html');
        let data2 = await mineReadFile('./resource/2.html');
        let data3 = await mineReadFile('./resource/3.html');
        console.log(data1 + data2 + data3);
    }catch(e){
        console.log(e.code);
    }
}

main();