【Promise】Promise 使用 / 回调地狱问题 async-await /宏队列与微队列

目录

一、复习回顾知识 

1、函数对象与实例对象

2、回调函数(重点理解)

2.1 什么是回调函数?

2.2 回调函数的分类

3、错误(Error)

3.1 错误的类型

3.2 错误处理

3.3 错误对象

二、Promise 的理解和使用

1、promise 理解

1.1 promise 是什么?

1.2 promise 的状态改变

1.3 promise 的基本流程

2、Promise 的使用

2.1、重要语法

2.2、基本编码流程

2.3 使用 1:promise 基本编码流程

2.4 使用 2:使用 promise 封装基于定时器的异步

2.5 使用 3:使用 promise 封装 ajax 异步请求

3、Promise 的 API

3.1 Promise 构造函数:new Promise (executor) { }

3.2 Promise.prototype.then 方法:Promise 实例. then(onFulfilled, onRejected)

3.3 Promise.prototype.catch 方法:Promise 实例. catch (onRejected)

3.4 Promise.resolve(value) / Promise.reject( reason )

3.5 Promise.all(promiseArr)

3.6 Promise.race(promiseArr)

3.7 Promise的几个关键问题

4、Promise 解决回调地狱问题

4.1 回调地狱

4.2 then 的链式调用

4.3 then 的链式调用解决回调地狱(不是最优秀的方法)

4.4 中断 Promise 链

4.5 错误的穿透

4.6 async - await 解决回调地狱(终极解决方案

5、Promise 优势

三、宏队列与微队列

1、概述

2、相关面试题

2.1 题型一

2.2 题型二

2.3 题型三


一、复习回顾知识 

1、函数对象与实例对象

函数对象:函数作为对象使用时,简称为函数对象。

    <script>
        function Person(name, age) {
            this.name = name;
            this.age = age;
        }
        Person.sex = 'boy';
        console.log(Person.sex);
        console.log(Person.name); // Person.name 默认是构造函数名,无法自己定义
    </script>

实例对象:new 构造函数或类产生的对象,我们称之为实例对象。

 const p = new Person('zs', 20);
 console.log(p); // Person {name: "zs", age: 20}

2、回调函数(重点理解)

2.1 什么是回调函数?

我们定义的,我们没有调用,最终执行了。

2.2 回调函数的分类

(1)同步的回调函数:

理解:立即在主线程上执行,不会放入回调队列中

举例:数组遍历相关的回调函数  / Promise 的 executor 函数。

(2)异步的回调函数:

理解:不会立即执行,会放入回调队列以后执行。

举例:定时器回调 / ajax 回调 / Promise 的成功、失败的回调。

JS 执行顺序:

事件循环:

3、错误(Error)

官方文档:Error - JavaScript | MDN

3.1 错误的类型

Error 所有错误的父类型
ReferenceError 引用的变量不存在
TypeError 数据类型不正确
SyntaxError 语法错误

3.2 错误处理

捕获错误:try{ } catch(){ }

MDN 文档:try...catch - JavaScript | MDN

try {
  nonExistentFunction();
} catch (error) {
  console.error(error);
  // expected output: ReferenceError: nonExistentFunction is not defined
  // Note - error messages will vary depending on browser
}

抛出错误:throw error

3.3 错误对象

message 属性:错误相关信息。

stack 属性:记录信息。

二、Promise 的理解和使用

1、promise 理解

1.1 promise 是什么?

抽象表达

(1)Promise 是一门新的技术(ES6 规范)。

(2)Promise 是 JS 中进行异步编程新解决方案(旧方案是:单纯使用回调函数)。

具体表达:

(1)从语法上来说:Promise 是一个构造函数。

(2)从功能上来说:promise 对象用来封装一个异步操作并可以获取其成功 / 失败的结果值。

理解:

(1)Promise 不是回调,是一个内置的构造函数,是程序员自己 new 调用的。

(2)new Promise 的时候,要传入一个回调函数(executor 函数),它是同步的回调会立即在主线程上执行。

(3)executor 函数会接收到 2 个参数,它们都是函数,分别用形参:resolve、reject 接收。

① 调用 resolve,会让 Promise 实例状态变为:成功 (fulfilled),同时可以指定成功的 value。

② 调用 reject,会让 Promise 实例状态变为:失败 (rejected),同时可以指定失败的 reason。

注:回调函数executor本身 是同步的,executor里面的函数是异步的。

1.2 promise 的状态改变

每一个 Promise 实例都有 3 种状态,分别是:初始化(pending)、成功(fulfilled)、失败(rejected)。

每一个 Promise 实例被 new 出来的那一刻,状态都是初始化(pending)

Promise 的状态只能改变一次,并且改变只有两种:pending => fulfilledpending => rejected

1.3 promise 的基本流程

2、Promise 的使用

2.1、重要语法

new Promise(executor) 构造函数
Promise.prototype.then() 方法

2.2、基本编码流程

1、创建Promise的实例对象(pending状态),传入executor函数
2、在executor中启动异步任务(定时器、ajax请求)
3、根据异步任务的结果,做不同处理:
        3.1 如果异步任务成功了:
            我们调用resolve(value),让Promise实例对象状态变为成功(fulfilled) ,同时指定成功的value
        3.2.如果异步任务失败了:
            我们调用reject(reason),让Promise实例对象状态变为失败(rejected),同时指定失败的reason
 

4、通过then方法为Promise的实例指定成功、失败的回调函数,来获取成功的value、失败的reason
        注意: then方法所指定的:成功的回调、失败的回调,都是异步的回调。

5、关于状态的注意点:
    1、三个状态:
        pending:未确定的 ----- 初始状态
        fulfilled:成功的 ------ 调用resolve( ) 后的状态
        rejected:失败的 ------ 调用reject( ) 后的状态
    2、两种状态改变
        pending => fulfilled
        pending => rejected
    3、状态只能改变一次! !
    4、一个promise指定多个成功/失败回调函数,都会调用吗? 会!


2.3 使用 1:promise 基本编码流程

<script>
// 1) 创建 promise 对象(pending 状态), 指定执行器函数
const p = new Promise((resolve, reject) => {
// 2) 在执行器函数中启动异步任务
setTimeout(() => {
    const time = Date.now()
    // 3) 根据结果做不同处理
    if (time % 2 === 1) {
        // 3.1) 如果成功了, 调用 resolve(), 指定成功的 value, 变为 resolved 状态
        resolve('成功的值 '+ time) } else { 
        // 3.2) 如果失败了, 调用 reject(), 指定失败的 reason, 变为rejected 状态
        reject('失败的值' + time) }
    }, 2000)
})
// 4) 能 promise 指定成功或失败的回调函数来获取成功的 vlaue 或失败的 reason
p.then(
    value => { // 成功的回调函数 onResolved, 得到成功的 vlaue
        console.log('成功的 value: ', value)
    },
    reason => { // 失败的回调函数 onRejected, 得到失败的 reason
        console.log('失败的 reason: ', reason) 
    } 
)
</script>

2.4 使用 2:使用 promise 封装基于定时器的异步

<script>
        function doDelay(time) {
            // 1. 创建 promise 对象
            return new Promise((resolve, reject) => {
                // 2. 启动异步任务
                console.log('启动异步任务')
                setTimeout(() => {
                    console.log('延迟任务开始执行...')
                    const time = Date.now() // 假设: 时间为奇数代表成功, 为偶数代表失败
                    if (time % 2 === 1) { // 成功了
                        // 3. 1. 如果成功了, 调用 resolve()并传入成功的 value
                        resolve('成功的数据 ' + time)
                    } else { // 失败了
                        // 3.2. 如果失败了, 调用 reject()并传入失败的 reason
                        reject('失败的数据 ' + time)
                    }
                }, time)
            })
        }
        const promise = doDelay(2000)
        promise.then(
            value => {
                console.log('成功的 value: ', value)
            },
            reason => {
                console.log('失败的 reason: ', reason)
            },
        )
    </script>

2.5 使用 3:使用 promise 封装 ajax 异步请求

 <script>
        /*
        可复用的发 ajax 请求的函数: xhr + promise
        */
        function promiseAjax(url) {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest()
                xhr.onreadystatechange = () => {
                    if (xhr.readyState !== 4) return
                    const { status, response } = xhr
                    // 请求成功, 调用 resolve(value)
                    if (status >= 200 && status < 300) {
                        resolve(JSON.parse(response))
                    } else { // 请求失败, 调用 reject(reason)
                        reject(new Error('请求失败: status: ' + status))
                    }
                }
                xhr.open("GET", url)
                xhr.send()
            })
        }
        promiseAjax('https://api.apiopen.top2/getJoke?page=1&count=2&type=video').then(
            data => {
                console.log('显示成功数据', data)
            },
            error => {
                alert(error.message)
            })
    </script>

3、Promise 的 API

3.1 Promise 构造函数:new Promise (executor) { }

executor 函数:是同步执行的,(resolve, reject) => { }

resolve 函数:调用 resolve 将 Promise 实例内部状态改为成功 (fulfiled)。

reject 函数:调用 reject 将 Promise 实例内部状态改为失败 (rejected)。

说明executor 函数会在 Promise 内部立即同步调用,异步代码放在 executor 函数中

3.2 Promise.prototype.then 方法:Promise 实例. then(onFulfilled, onRejected)

onFulfilled:成功的回调函数 —— (value) => { }

onRejected:失败的回调函数 —— (reason) => { }

注意:then 方法会返回一个新的 Promise 实例对象。

3.3 Promise.prototype.catch 方法:Promise 实例. catch (onRejected)

onRejected失败的回调函数 —— (reason) => { }

说明:catch 方法是 then 方法的语法糖,相当于:then(undefined, onRejected)

executor里执行的是reject函数:

第一个p.then方法里的回调函数只指定了成功的回调,而没有指定失败的回调,会产生报错未捕获异常(Uncaught ...

第二个p.catch方法实际上是p.then的语法糖,它默认不指定成功的回调,只指定失败的回调,然后输出reject函数的信息。

另外:如果executor里执行的是resolve函数,而下面没有指定成功的回调,不会报错。

3.4 Promise.resolve(value) / Promise.reject( reason )

Promise.resolve(value):

说明:用于快速返回一个状态为 fulfilled 或rejected的 Promise 实例对象

备注:value 的值可能是:(1)非 Promise 值,例如数组,字符串等等(2)Promise 值。

① 如果先调用了reject,返回失败的Promise值,再调用resolve,最后仍然返回失败的Promise值。

②如果先调用了resolve,返回成功的Promise值,再调用reject,最后执行失败的Promise回调函数,但reason是之前的成功的Promise值

 Promise.reject(reason):用于快速返回一个状态必为 rejected 的 Promise 实例对象。

 ​         

    

// 测试:如果方法给的是 p0 参数,打印出各种可能的结果
    // 为了方便看,不进行代码的注释。
    <script>
        // resolve - resolve
        const p0 = Promise.resolve('ok');
        const p = Promise.resolve(p0); // succ:  ok
 
        // reject - reject
        const p0 = Promise.reject('no!');
        const p = Promise.reject(p0); //fail:  Promise {<rejected>: "no!"} + 报错:Uncaught (in promise) no!
 
        // resolve - reject
        const p0 = Promise.resolve('ok');
        const p = Promise.reject(p0); // fail:  Promise {<fulfilled>: "ok"}
 
        // reject - resolve
        const p0 = Promise.reject('no!');
        const p = Promise.resolve(p0); // fail:  no!
 
        p.then(
            (value) => { console.log('succ: ', value); },
            (reason) => { console.log('fail: ', reason); }
        )
    </script>

3.5 Promise.all(promiseArr)

promiseArr:包含 n 个 Promise 实例的数组。

说明:返回一个新的 Promise 实例,只有所有的 promise 都成功才成功,成功的值是所有Promise成功的值的集合;只要有一个失败了就直接失败一遇到失败的Promise就直接返回失败的值,不关心后面的promise值。

    <script>
        const p1 = Promise.resolve('0');
        const p2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                // resolve('500');
                reject('500');
            }, 500)
        });
        const p3 = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('2000');
            }, 2000)
        })
        const x = Promise.all([p1, p2, p3]);
        const then = x.then(
            (value) => { console.log('success', value); },
            (reason) => { console.log('fail', reason); }   // 'fail', '2000'
        )
    </script>

3.6 Promise.race(promiseArr)

promiseArr:包含 n 个 Promise 实例的数组。

说明:返回一个新的 Promise 实例,返回第一个promise实例的值。

3.7 Promise的几个关键问题

1、如何改变一个Promise实例的状态?

  • 执行 resolve (value):如果当前是pending就会变为fulfilled
  • 执行 reject (reason):如果当前是pending就会变为rejected
  • 执行器函数 (executor) 抛出异常:如果当前是pending就会变为rejected

Promise状态只能改变一次,上面会返回成功,后面的未定义异常不会引起Promise状态改变。

2、改变Promise实例的状态和指定回调函数谁先谁后?
        ①都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调


       ②如何先改状态再指定回调?
                延迟一会 再调用 then( )


        ③Promise实例什么时候才能得到数据?
                如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据。
                如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据。

3、Promise实例.then()返回的是一个【新的Promise实例】,它的值和状态由什么决定? 
                1、简单表达:then( ) 所指定的回调函数执行的结果决定 。
                2、详细表达:
                        (1) 如果then所指定的回调返回的是 非Promise值 a:
                                    那么【新Promise实例】状态为:成功(fulfilled),成功的value为a 。
                        (2) 如果then所指定的回调返回的是一个Promise实例 p:
                                    那么【新Promise实例】的状态、值,都与p一致
                        (3) 如果then所指定的回调抛出异常: 
                                    那么【新Promise实例】状态为rejected,reason为抛出的那个异常。

      

 p.then输出成功了1,a,返回值为900,是非Promise值,

然后调用x.then,返回成功了2,900。

     

 p.then输出失败了1,a,返回值为undefined,是非Promise值,

然后调用x.then,返回成功了2,undefined。

   

    

4、Promise 解决回调地狱问题

4.1 回调地狱

什么是回调地狱?

回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件。

例如:要求在第一次请求成功后开启第二次请求,第二次请求后开启第三次请求,导致层层嵌套。

回调地狱的缺点:不便于程序员阅读,不便于异常处理,不便于后期维护。

4.2 then 的链式调用

Promise 实例. then( ) 返回的是一个新的 Promise 实例,它的值和状态由 then() 所指定的回调函数执行的结果决定。

(1)如果 then 所指定的回调返回的是非 Promise 值 a:那新的 Promise 实例状态为成功 (fulfillled),成功的 value 为 a。

    <script>
        const p = new Promise((resolve, reject) => {
            resolve('a');
        })
 
        const x = p.then(
            (value) => { console.log('succ_then1:', value); }, // succ_then1: a
            (reason) => { console.log('fail_then1:', reason); }
        )
 
        x.then(
            (value) => { console.log('succ_then2:', value); }, // succ_then2: undefined
            (reason) => { console.log('fail_then2:', reason); }
        )
    </script>
    <script>
        const p = new Promise((resolve, reject) => {
            resolve('a');
        })
 
        const x = p.then(
            (value) => { console.log('succ_then1:', value); return false }, // succ_then1: a
            (reason) => { console.log('fail_then1:', reason); }
        )
 
        x.then(
            (value) => { console.log('succ_then2:', value); }, // succ_then2: false
            (reason) => { console.log('fail_then2:', reason); }
        )
    </script>

(2)如果 then 所指定的回调返回的是 Promise 实例 p:那新的 Promise 实例的状态、值都与 p 一致。

    <script>
        const p = new Promise((resolve, reject) => {
            resolve('a');
        })
 
        const x = p.then(
            (value) => { console.log('succ_then1:', value); return Promise.resolve('a') }, // succ_then1: a
            (reason) => { console.log('fail_then1:', reason); }
        )
 
        x.then(
            (value) => { console.log('succ_then2:', value); }, // succ_then2: a
            (reason) => { console.log('fail_then2:', reason); }
        )
    </script>
    <script>
        const p = new Promise((resolve, reject) => {
            resolve('a');
        })
 
        const x = p.then(
            (value) => { console.log('succ_then1:', value); return Promise.reject('a') }, // succ_then1: a
            (reason) => { console.log('fail_then1:', reason); }
        )
 
        x.then(
            (value) => { console.log('succ_then2:', value); },
            (reason) => { console.log('fail_then2:', reason); } // fail_then2: a
        )
    </script>

(3)如果 then 所指定的回调抛出异常:那新的 Promise 实例状态为 rejected,reason 为抛出的异常。

    <script>
        const p = new Promise((resolve, reject) => {
            resolve('a');
        })
 
        const x = p.then(
            (value) => { console.log('succ_then1:', value); throw 404 }, // succ_then1: a
            (reason) => { console.log('fail_then1:', reason); }
        )
 
        x.then(
            (value) => { console.log('succ_then2:', value); }, 
            (reason) => { console.log('fail_then2:', reason); } // fail_then2: 404
        )
    </script>

综合案例:

    <script>
        const p = new Promise((resolve, reject) => {
            resolve('a');
        })
 
        p.then(
            (value) => { console.log('succ_then1:', value); throw 404 }, // succ_then1: a
            (reason) => { console.log('fail_then1:', reason); return 10 }
        ).then(
            (value) => { console.log('succ_then2:', value); return 100 },
            (reason) => { console.log('fail_then2:', reason); return Promise.reject('20') } // fail_then2: 404
        ).then(
            (value) => { console.log('succ_then3:', value); return true },
            (reason) => { console.log('fail_then3:', reason); return false } // fail_then3: 20
        ).then(
            (value) => { console.log('succ_then4:', value); },
            (reason) => { console.log('fail_then4:', reason); } // succ_then4: false
        )
    </script>

4.3 then 的链式调用解决回调地狱(不是最优秀的方法)

解释:第一次请求成功的时候,调用 value => { } 这个成功的回调,返回的是一个 Promise 实例 (第二次请求的实例)。由上一点 then() 方法的相关说明可知,如果 then 所指定的回调返回的是 Promise 实例 p,那新的 Promise 实例的状态、值都与 p 一致。所以实际上我们是将第二次请求的 Promise 实例作为第一个 then 的返回值传递下去。

        promiseAjax(url)
            .then(
                value => {
                    console.log('显示第1次成功的数据', value);
                    // 返回第二次请求的实例
                    return promiseAjax(url)
                },
                reason => { alert(reason.message); }
            )

完整 code:

    <script>
        // 定义一个发送请求的函数,返回一个新的 Promise 实例(封装了 ajsx 异步任务)
        function promiseAjax(url) {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.onreadystatechange = () => {
                    if (xhr.readyState !== 4) return;
                    const { status, response } = xhr;
                    if (status >= 200 && status < 300) {
                        // 请求成功, 调用 resolve(value)
                        resolve(JSON.parse(response));
                    } else {
                        // 请求失败, 调用 reject(reason)
                        reject(new Error('请求失败: status: ' + status));
                    }
                }
                xhr.open("GET", url);
                xhr.send();
            })
        }
        
        // 定义正确和错误的 url 地址便于之后的测试
        const url = 'https://api.apiopen.top/api/getHaoKanVideo?page=0&size=2';
        const url_error = 'https://api.apiopen.top/api22/getHaoKanVideo?page=0&size=2';
 
        // then 的链式调用解决回调地狱问题
        promiseAjax(url)
            .then(
                value => {
                    console.log('显示第1次成功的数据', value);
                    return promiseAjax(url)
                },
                reason => { alert(reason.message); }
            )
            .then(
                value => {
                    console.log('显示第2次成功的数据', value);
                    return promiseAjax(url)
                },
                reason => { alert(reason.message); }
            )
            .then(
                value => {
                    console.log('显示第3次成功的数据', value);
                    return promiseAjax(url)
                },
                reason => { alert(reason.message); }
            )
            .then(
                value => {
                    console.log('显示第4次成功的数据', value);
                    return promiseAjax(url)
                },
                reason => { alert(reason.message); }
            )
 
    </script>

4.4 中断 Promise 链

问题产生:如果我们按照 4.3 的代码运行,假设第 2 次请求发送失败,那么会抛出一个 ERROR 错误,调用 reason => {} 失败的回调,但可惜的是我们并没有给失败的回调返回值,所以返回 undefined。而 undefined 恰好是非 Promise 值,这导致在之后的请求中变为成功!这显然是不对的。

解决的问题:请求失败后就不再继续请求。

解决方法:为每一个失败的回调返回一个状态为 pending 的 Promise 实例。

        promiseAjax(url)
            .then(
                value => {
                    console.log('显示第1次成功的数据', value);
                    return promiseAjax(url_error)
                },
                reason => { alert(reason.message); return new Promise(() => { }) }
            )
            .then(
                value => {
                    console.log('显示第2次成功的数据', value);
                    return promiseAjax(url)
                },
                reason => { alert(reason.message); return new Promise(() => { }) }
            )

4.5 错误的穿透

问题的产生:我们在每一个 then() 方法中都要指定失败的回调,并且需要返回值以中断 Promise 链,这些失败的回调代码极其相似,显得繁琐。

解决方案:使用 .catch() 方法兜底。

基本思想:我们使用 .catch() 方法为所有错误指定一个失败的回调,实际的思想是,虽然我们没有在 then() 方法中指定失败的回调,但是底层为我们补了 reason => {throw reason},如果 then 所指定的回调抛出异常,那新的 Promise 实例状态为 rejected,reason 为抛出的异常。所以实际上这个 reason 不断通过失败的回调传递给 .catch() 方法,最终抛出异常。

        promiseAjax(url)
            .then(
                value => {
                    console.log('显示第1次成功的数据', value);
                    return promiseAjax(url_error)
                },
                // 我们虽然没有写失败的回调,但实际上底层为我们补了以下代码:
                // reason => {throw reason}
            )
            .then(
                value => {
                    console.log('显示第2次成功的数据', value);
                    return promiseAjax(url)
                },
                // 我们虽然没有写失败的回调,但实际上底层为我们补了以下代码:
                reason => {throw reason}
            )
            .catch(
                reason => { alert(reason.message); }
            )

4.6 async - await 解决回调地狱(终极解决方案)

语法说明:

(1)async 修饰的函数:

① 函数的返回值为 promise 对象。

② promise 实例的结果由 async 函数执行的返回值决定。

(2)await 表达式:

① 如果表达式是 promise 实例对象,await 后的返回值是 promise 成功的值。

② 如果表达式是其他值,直接将此值作为 await 的返回值(相当于赋值操作,好像没啥用的样子...)

(3)注意:

await 必须写在 async 函数中,但 async 函数中可以没有 await(单纯函数前面写 async 好像也没什么用的样子...)。

如果 await 的 promise 实例对象失败了,就会抛出异常,需要通过 try...catch 来捕获处理。

async - await 语法:

// 函数形式写法
async function demo() {
    try {
        const result = await promiseAjax(url);
        console.log(result);
        console.log(100);
    } catch (error) {
        console.log(error);
    }
}
demo();
 
// 箭头函数形式写法
// 注意:如果前面没分号,需要在函数前面加分号或者感叹号,否则会报错。
(async () => {
    try {
        const result = await promiseAjax(url);
        console.log(result);
        console.log(100);
    } catch (error) {
        console.log(error);
    }
})()

await 的原理:

若我们使用 async 配合 await 这种写法:
1、表面上不出现任何的回调函数。
2、但实际上底层把我们写的代码进行了加工,把回调函数 “还原” 回来了。
3、最终运行的代码是依然有回调的,只是程序员没有看见。

        (async () => {
            try {
                // 程序员“实际上”的写法
                const result = await promiseAjax(url);
                console.log(result);
                console.log(100);
 
                // 浏览器翻译后的代码(表面上没有调then,实际上调了then)
                promiseAjax(url).then(
                     (value) => {
                         console.log(value);
                         console.log(100);
                     }
                 )
            } catch (error) {
                console.log(error);
            }
        })()
        console.log('主线程');
        // 输出:先执行主线程,函数瞬间调用完,将异步函数推进队列,等待调用执行。
        //      主线程
        //      {code: 200, message: "成功!", result: {…}}
        //      100

async / await 请求成功 

as失败

5、Promise 优势

优势:
1、指定回调函数的方式更加灵活:
        旧的:必须在启动异步任务前指定
        promise:启动异步任务=〉返回promie对象=〉给promise对象绑定回调函数(甚至可以在异步任务结束后指定)

2、支持链式调用,可以解决回调地狱问题
        (1)什么是回调地狱:
                回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件。

        (2)回调地狱的弊病:
                代码不便于阅读、不便于异常的处理。

        (3)一个不是很优秀的解决方案:
                then的链式调用
        (4)终极解决方案:
                async/ await (底层实际上依然使用then的链式调用)

三、宏队列与微队列

1、概述

JS 中用来存储待执行回调函数的队列包含 2 个不同特定的列队:
        宏队列:用来保存待执行的宏任务(回调),比如:定时器回调、DOM 事件回调、ajax 回调。

        微队列:用来保存待执行的微任务(回调),比如:promise 的回调、MutationObserver 的回调。目前接触到的微任务就只有Promise的回调。

        执行:JS 引擎首先必须先执行所有的初始化同步任务代码,每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行,也就是微任务优先级比宏任务高,微任务执行完之后再执行宏任务,且与微任务所处的代码位置无关。

参考:JS 异步之宏队列与微队列 - BAHG - 博客园

2、相关面试题

宏队列:[宏任务1,宏任务2.....]
微队列:[微任务1.微任务.....]
规则:每次要执行宏队列里的一个任务之前,先看微队列里是否有待执行的微任务。
        1、如果有,先执行微任务。
        2、如果没有,按照宏队列里任务的顺序,依次执行。

2.1 题型一

微任务先于宏任务执行。

        setTimeout(() => {
            console.log('timeout1');
        })
        setTimeout(() => {
            console.log('timeout2');
        });
        Promise.resolve(3).then(
            value => { console.log('成功了1'); }
        )
        Promise.resolve(4).then(
            value => { console.log('成功了2'); }
        )

2.2 题型二

宏任务里面调用了微任务:

宏任务队列:3s后 timeout2; 5s 后 timeout1, 然后将 timeout3推入宏任务再将成功了5 推入微任务

微任务队列:成功了3;成功了4;成功了5

执行结果:微任务先执行:成功了3;成功了4;宏任务执行:间隔3秒的 timeout2 先执行,间隔5s的 timeout1 再执行,然后将 timeout3推入宏任务再将成功了5 推入微任务微任务先执行成功了5,然后执行宏任务timeout3。

注:宏任务里面可能存在将微任务放入微队列,这时候微队列先执行。

        setTimeout(() => {
            console.log('timeout1');
            setTimeout(() => {
                console.log('timeout3');
            })
            Promise.resolve(5).then(
                value => { console.log('成功了5'); }
            )
        }, 5000)
        setTimeout(() => {
            console.log('timeout2');
        }, 3000)
        Promise.resolve(3).then(
            value => { console.log('成功了3'); }
        )
        Promise.resolve(4).then(
            value => { console.log('成功了4'); }
        )

2.3 题型三

    <script>
        setTimeout(() => {
            console.log('0');
        });
        new Promise((resolve, reject) => {
            console.log('1');
            resolve();
        }).then(() => {
            console.log('2');
            new Promise((resolve, reject) => {
                console.log('3');
                resolve();
            }).then(() => {
                console.log('4');
            }).then(() => {
                console.log('5');
            })
        }).then(() => {
            console.log('6');
        })
        new Promise((resolve, reject) => {
            console.log('7');
            resolve();
        }).then(() => {
            console.log('8');
        })
    </script>

 

 

输出:1 7 2 3 8 4 6 5 0

猜你喜欢

转载自blog.csdn.net/qq_37308779/article/details/126130378