web前端面试高频考点——JavaScript 篇(二)【JS 异步进阶】Event Loop、then 和 catch、async/await、宏任务微任务、手撕 Promise 源码

简介: web前端面试高频考点——JavaScript 篇(二)【JS 异步进阶】Event Loop、then 和 catch、async/await、宏任务微任务、手撕 Promise 源码

JS 异步进阶

什么是 event loop(事件循环/事件轮询)

  • JS 是单线程运行的
  • 异步要基于回调来实现
  • event loop 就是异步回调的实现原理

JS 如何执行?

从前到后,一行一行执行

如果某一行执行报错,则停止下面代码的执行

先把同步代码执行完,再执行异步

event loop 执行过程

event loop 过程 1


同步代码,一行一行放在 Call Stack 执行

遇到异步,会先记录下来,等待时机(定时、网络请求等)

时机到了,就移动到 Callback Queue

event loop 过程 2

  • 如果 Call Stack 为空(即同步代码执行完)Event loop 开始工作
  • 轮询查找 Callback Queue,如有则移动到 Call Stack 执行
  • 然后继续轮询查找(永动机一样)

图解代码执行过程:

    console.log('Hi')
    setTimeout(function cb1() {
        console.log('cb1')
    }, 2000)
    console.log('Bye')

图片出处:https://coding.imooc.com/lesson/400.html#mid=35171

fd76a88fb3bd45ab8b7c6a946ef7ea54.png

f17baf04dd03467ca49415ba4d6a22ca.png

136ad4c4d03c43b9a0875cd2db59a947.png

423a3a6329074aa18de09df711c2e78c.png

DOM 事件与 event loop

  • JS 是单线程
  • 异步(setTimeout,ajax等)使用回调,基于 event loop
  • DOM 事件也使用回调,基于 event loop

Promise 三种状态

三种状态

  • pending resolve rejected
  • pending => resolve 或 pending => rejected
  • 变化不可逆

状态的表现

  • pending 状态,不会触发 then 和 catch
  • resolved 状态,会触发后续的 then 回调函数
  • rejected 状态,会触发后续的 catch 回调函数

成功的回调:只执行 then,不会执行 catch

  // 输出结果:data 100
    const p1 = Promise.resolve('100')
    p1.then(data => {
        console.log('data', data)
    }).catch(err => {
        console.log('err', err)
    })

失败的回调:只执行 catch,不执行 then

  // 输出结果:err2 -100
    const p2 = Promise.reject('-100')
    p2.then(data => {
        console.log('data2', data)
    }).catch(err => {
        console.log('err2', err)
    })

then 和 catch 改变状态

  • then 正常返回 resolved,里面有报错则返回 rejected
  • catch 正常返回 resolved,里面有报错则返回 rejected

第一种情况:

    const p1 = Promise.resolve().then(() => {
        return 100
    })
    console.log('p1', p1);
    p1.then(() => {
        console.log('200')
    })
    const p2 = Promise.resolve().then(() => {
        throw new Error('then error')
    })
    console.log('p2', p2)
    p2.then(() => {
        console.log('300')
    }).catch(err => {
        console.log('error', err)
    })

468664c1939a4c5f8e92ec3089b4a10e.png

第二种情况:

  const p3 = Promise.reject('my error').catch(err => {
        console.log(err)
    })
    console.log('p3', p3)
    p3.then(() => {
        console.log(100)
    })
    const p4 = Promise.reject('my error').catch(err => {
        throw new Error('catch err')
    })
    console.log('p4', p4)
    p4.then(() => {
        console.log(200)
    }).catch(() => {
        console.log('some err')
    })

a73c062a89084760b2c8f5f7a10d35a7.png

Promise 关于 then 和 catch 的面试题

示例 1 :resolve 成功输出 1 后又是一个成功的回调(fulfilled),之后执行 then,不会执行 catch

  // 输出结果:1 3
  Promise.resolve().then(() => {
        console.log(1)
    }).catch(() => {
        console.log(2)
    }).then(() => {
        console.log(3)
    })

示例 2:resolve 成功输出 1 后报错异常(rejected),之后执行 catch 输出 2 后(fulfilled),执行 then

  // 输出结果:1 2 3 
    Promise.resolve().then(() => {
        console.log(1)
        throw new Error('error1')
    }).catch(() => {
        console.log(2)
    }).then(() => {
        console.log(3)
    })

示例 3:resolve 成功输出 1 后报错异常(rejected),之后执行 catch 输出 2 后(fulfilled),之后就结束了

  // 输出:1 2
    Promise.resolve().then(() => {
        console.log(1)
        throw new Error('error1')
    }).catch(() => {
        console.log(2)
    }).catch(() => {
        console.log(3)
    })

async 和 await 基本使用

  • 使用 await,必须有 async 包裹
  • ! 的作用是防止此前代码结尾不加分号
    // 加载图片
    function loadImg(src) {
        const p = new Promise((resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`图片加载失败${src}`)
                reject(err)
            }
            img.src = src
            document.body.appendChild(img)
        })
        return p
    }
    const src = 'xxx.png'
    !(async function() {
        const img = await loadImg(src)
        console.log(img.width, img.height) // 100 110
    })()

async/await 和 Promise 的关系

async/awiat 是消灭异步回调的终极武器

但和 Promise 并不互斥

反而,两者相辅相成

执行 async 函数,返回的是 Promise 对象

await 相当于 Promise 的 then

try…catch 可捕获异常,代替了 Promise 的 catch

示例 1:执行 async 函数,返回的是一个 Promise 对象

    async function fn1(){
        // return 100 //相当于 return Promise.resolve(100)
        return Promise.resolve(200)
    }
    const res1 = fn1() // 执行 async 函数,返回的是一个 Promise 对象
    console.log('res1', res1);
    res1.then(data => {
        console.log('data', data) // 200
    })

826ad17ac436490baa01b1bc61d922c1.png

示例 2:

    async function fn() {
        return Promise.resolve(200)
    }
    !(async function() {
        const p1 = Promise.resolve(300)
        const data = await p1 // await 相当于 Promise 的 then
        console.log('data', data)
    })()
    !(async function() {
        const data1 = await 400 // await Promise.resolve(400)
        console.log('data1', data1)
    })()
    !(async function() {
        const data2 = await fn()
        console.log('data2', data2);
    })()

c6e082c4e00f45849954aa04b3d5127f.png

示例 3:try…catch 异常捕获

    !(async function() {
        const p4 = Promise.reject('error1')
        try {
            const res = await p4
            console.log(res)
        } catch (err) {
            console.error(err) // try...catch 相当于 promise catch
        }
    })()

51b5b45ca3224c39a605121f15c0a0e0.png

示例 4:

    !(async function() {
        const p5 = Promise.reject('err') // rejected 状态
        const res = await p5 // await -> then
        console.log('res', res) // 不会执行
    })()

e887f010446c47639c54b37043af3cd4.png

异步的本质

  • 异步的本质是回调函数
  • async/await 是消灭异步回调的终极武器
  • JS 是单线程,还得是有异步,还得是基于 event loop
  • async/await 是一个很好的语法糖

示例:await 后面的内容是 异步的

    async function async1() {
        console.log('async1 start') // 2
        await async2() // undefined
        // await 的后面,都可以看作是 callback 里的内容,即异步
        // 类似,event loop,setTimeout()
        console.log('async1 end') // 5
    }
    async function async2() {
        console.log('async2') // 3
    }
    console.log('script start') // 1
    async1()
    console.log('script end') // 4 

输出顺序:

5fd44faf7d5c43119346186b65462c5c.png

示例 2:注——await 后面的都是异步内容,等主线程执行完才会触发 event loop 机制执行

  async function async1() {
        console.log('async1 start') // 2
        await async2() // undefined
        // 下面三行都是异步回调 callback 的内容
        console.log('async1 end') // 5
        await async3()
        console.log('async1 end 2') // 7
    }
    async function async2() {
        console.log('async2') // 3
    }
    async function async3() {
        console.log('async3') // 6
    }
    console.log('script start') // 1
    async1()
    console.log('script end') // 4
    // 同步代码执行完,event loop

936f05e9b5fd4559b657453788dd234c.png

宏任务与微任务

  • 宏任务:setTimeout、setInterval、Ajax、DOM事件
  • 微任务:Promise、async/await
  • 微任务执行时机比宏任务要早

Event Loop 和 DOM 渲染

每次 Call Stack 清空(即每次轮询结束)

都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染

然后再去触发下一次 Event Loop

图片出处:https://coding.imooc.com/lesson/400.html#mid=35181

9d40f121fffe49b89bde9c7bea762a9f.png

宏任务和微任务的区别

  • 宏任务:DOM 渲染后触发,如 setTimeout
  • 微任务:DOM 渲染前触发,如 Promise

宏任务和微任务的根本区别

  • 在 micro task queue 里执行当前的微任务
  • 微任务比宏任务的执行时机更早
  • 微任务发生在 DOM 渲染之前
  • 宏任务发生在 DOM 渲染之后
  • 29bf482e159f41d6ba63b5ee8df4a29c.png

JS 异步面试题

什么是宏任务和微任务、两者的区别

  • 宏任务:setTimeout,setInterval,Ajax,DOM 事件
  • 微任务:Promise,async/await
  • 微任务执行时机比宏任务要早

场景题 — async、await 语法

  • async 修饰的函数返回的是 promise 实例
  • a 没有 await 修饰,输出的是 promise 实例
  • b 有 await 修饰,相当于 then ,输出的是 100

示例 1:

    async function fn() {
        return 100
    }
    (async function() {
        const a = fn()
        const b = await fn()
        console.log(a)
        console.log(b)
    })()

b2dc0b51c9fa4a1ba42a9d95e5cd98a6.png

示例 2:失败后面的代码都不会执行

    (async function() {
        console.log('start')
        const a = await 100
        console.log('a', a)
        const b = await Promise.resolve(200)
        console.log('b', b)
        const c = await Promise.reject(300)
        console.log('c', c)
        console.log('end')
    })()

e631fe81c3f841f9adae28b025491ddf.png

e65f5d072f324870acbf7912983dfc6c.png

promise 和 setTimeout 的顺序问题

  • 先执行主线程的任务
  • 再执行微任务
  • 再执行宏任务

示例:

    console.log(100)
    setTimeout(() => {
        console.log(200)
    })
    Promise.resolve().then(() => {
        console.log(300)
    })
    console.log(400)

370490a8e03743169f05b340b1c13430.png

async/await 的顺序问题

  • 主线程 => 微任务 => 宏任务
  • 初始化 promise 时,传入的函数会立刻被执行(主线程任务)
    async function async1 () {
        console.log('async1 start') // 2
        await async2()
        // await 后面的都作为回调内容 —— 微任务
        console.log('async1 end') // 6
    }
    async function async2 () {
        console.log('async2') // 3
    }
    console.log('script start') // 1
    setTimeout(function () { // 宏任务
        console.log('setTimeout') // 8
    }, 0)
    async1()
    // 初始化 promise 时,传入的函数会立刻被执行
    new Promise (function (resolve) {
        console.log('promise1') // 4
        resolve()
    }).then(function () { // 微任务
        console.log('promise2') // 7
    })
    console.log('script end') // 5
    // 同步代码执行完毕(event loop —— call stack 被清空)
    // 执行微任务
    // 尝试触发 DOM 渲染
    // 触发 Event Loop,执行宏任务

0a1574c362d0411f989fe7ce0d1f1e4d.png

手写 Promise 源码

  • 有难度有深度
    /**
     * @description MyPromise
     * @author 前端杂货铺
     */
    class MyPromise {
        state = 'pending' // 状态,'pending' 'fulfilled' 'rejected'
        value = undefined // 成功后的值
        reason = undefined // 失败后的原因
        resolveCallbacks = [] // pending 状态下,存储成功的回调
        rejectCallbacks = [] // pending 状态下,存储失败的回调
        constructor(fn) {
            const resolveHandler = (value) => {
                if (this.state === 'pending') {
                    this.state = 'fulfilled'
                    this.value = value
                    this.resolveCallbacks.forEach(fn => fn(this.value))
                }
            }
            const rejectHandler = (reason) => {
                if (this.state === 'pending') {
                    this.state = 'rejected'
                    this.reason = reason
                    this.rejectCallbacks.forEach(fn => fn(this.reason))
                }
            }
            try {
                fn(resolveHandler, rejectHandler)
            } catch (err) {
                rejectHandler(err)
            }
        }
        then(fn1, fn2) {
            fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
            fn2 = typeof fn2 === 'function' ? fn2 : (e) => e
            if (this.state === 'pending') {
                const p1 = new MyPromise((resolve, reject) => {
                    this.resolveCallbacks.push(() => {
                        try {
                            const newValue = fn1(this.value)
                            resolve(newValue)
                        } catch (err) {
                            reject(err)
                        }
                    })
                    this.rejectCallbacks.push(() => {
                        try {
                            const newReason = fn2(this.reason)
                            reject(newReason)
                        } catch (err) {
                            reject(err)
                        }
                    })
                })
                return p1
            }
            if (this.state === 'fulfilled') {
                const p1 = new MyPromise((resolve, reject) => {
                    try {
                        const newValue = fn1(this.value)
                        resolve(newValue)
                    } catch (err) {
                        reject(err)
                    }
                })
                return p1
            }
            if (this.state === 'rejected') {
                const p1 = new MyPromise((resolve, reject) => {
                    try {
                        const newReason = fn2(this.reason)
                        reject(newReason)
                    } catch (err) {
                        reject(err)
                    }
                })
                return p1
            }
        }
        // 就是 then 的一个语法糖,简单模式
        catch (fn) {
            return this.then(null, fn)
        }
    }
    MyPromise.resolve = function (value) {
        return new MyPromise((resolve, reject) => resolve(value))
    }
    MyPromise.reject = function (reason) {
        return new MyPromise((resolve, reject) => reject(reason))
    }
    MyPromise.all = function (promiseList = []) {
        const p1 = new MyPromise((resolve, reject) => {
            const result = [] // 存储 promiseList 所有的结果
            const length = promiseList.length
            let resolvedCount = 0
            promiseList.forEach(p => {
                p.then(data => {
                    result.push(data)
                    // resolvedCount 必须在 then 里面做 ++
                    // 不能用 index
                    resolvedCount++
                    if (resolvedCount === length) {
                        // 已经遍历到了最后一个 promise
                        resolve(result)
                    }
                }).catch(err => {
                    reject(err)
                })
            })
        })
        return p1
    }
    MyPromise.race = function (promiseList = []) {
        let resolved = false // 标记
        const p1 = new Promise((resolve, reject) => {
            promiseList.forEach(p => {
                p.then(data => {
                    if (!resolved) {
                        resolve(data)
                        resolved = true
                    }
                }).catch((err) => {
                    reject(err)
                })
            })
        })
        return p1
    }

不积跬步无以至千里 不积小流无以成江海

相关文章
|
12天前
|
Web App开发 移动开发 HTML5
html5 + Three.js 3D风雪封印在棱镜中的梅花鹿动效源码
html5 + Three.js 3D风雪封印在棱镜中的梅花鹿动效源码。画面中心是悬浮于空的梅花鹿,其四周由白色线段组成了一个6边形将中心的梅花鹿包裹其中。四周漂浮的白雪随着多边形的转动而同步旋转。建议使用支持HTML5与css3效果较好的火狐(Firefox)或谷歌(Chrome)等浏览器预览本源码。
42 2
|
25天前
|
缓存 JavaScript 前端开发
掌握现代JavaScript异步编程:Promises、Async/Await与性能优化
本文深入探讨了现代JavaScript异步编程的核心概念,包括Promises和Async/Await的使用方法、最佳实践及其在性能优化中的应用,通过实例讲解了如何高效地进行异步操作,提高代码质量和应用性能。
|
28天前
|
前端开发 JavaScript Java
一文带你了解和使用js中的Promise
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,正向全栈进发。如果我的文章对你有帮助,请关注我,将持续更新更多优质内容!🎉🎉🎉
21 0
一文带你了解和使用js中的Promise
|
1月前
ractive.js联系表单动画效果源码
一款ractive.js联系表单动画效果,很有创意的发送邮件、联系内容等表单,基于ractive.js实现的动画效果,以发送信件的方式。
24 1
|
1月前
|
前端开发 JavaScript
用HTML CSS JS打造企业级官网 —— 源码直接可用
必看!用HTML+CSS+JS打造企业级官网-源码直接可用,文章代码仅用于学习,禁止用于商业
124 1
|
1月前
|
JavaScript
JS趣味打字金鱼小游戏特效源码
hi fish是一款打字趣味小游戏,捞出海里的鱼,捞的越多越好。这款游戏用于电脑初学者练习打字。初学者可以根据自己的水平设置游戏难度。本段代码可以在各个网页使用,有需要的朋友可以直接下载使用,本段代码兼容目前最新的各类主流浏览器,是一款非常优秀的特效源码!
32 3
|
1月前
|
移动开发 HTML5
html5+three.js公路开车小游戏源码
html5公路开车小游戏是一款html5基于three.js制作的汽车开车小游戏源代码,在公路上开车网页小游戏源代码。
57 0
html5+three.js公路开车小游戏源码
|
24天前
JS+CSS3文章内容背景黑白切换源码
JS+CSS3文章内容背景黑白切换源码是一款基于JS+CSS3制作的简单网页文章文字内容背景颜色黑白切换效果。
17 0
|
7月前
|
前端开发 JavaScript
如何处理 JavaScript 中的异步操作和 Promise?
如何处理 JavaScript 中的异步操作和 Promise?
69 1
|
7月前
|
前端开发 JavaScript
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
105 4