• Promise的使用与async/await的使用



    js的运行程序是单线程,所有的队列再一个线程中完成。一旦遇到大量任务或者遇到一个耗时的任务,比如加载一个高清图片,网页就会出现"假死",因为JavaScript停不下来,也就无法响应用户的行为。 那么js是如何执行的呢?

    • 从前到后,一行一行执行
    • 如果某一行执行报错,则停止下面代码的执行
    • 先把同步的代码执行完,再执行异步

    为了防止主线程的阻塞,JavaScript 有了 同步异步的概念

    1. 同步:可以立即得到预期结果;
    2. 异步:要基于回调去返回得到结果;而event loop就是异步回调实现的原理;

    event loop(事件循环/事件轮询)

    循环的机制:
    在这里插入图片描述
    执行的步骤:

    1. 同步代码,一行一行放在Call stack执行
    2. 遇到异步 - 等待时机(会先记录 - 定时/网络)
    3. 时机到了,就移动到Callback Queue
    4. Callback Queue的为空;(即同步代码执行完)
    5. 轮询查找Callback Queue,如有就移到Callback stack执行;
    6. 继续的轮询查找(永动机一样);

    异步(setTimeout ,ajax等)使用回调,基于event loop

    promise

    先回顾一下 Promise 的基本使用:

    // 加载图片
    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
            }
        )
        return p
    }
    const url = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
    loadImg(url).then(img => {
        console.log(img.width)
        return img
    }).then(img => {
        console.log(img.height)
    }).catch(ex => console.error(ex))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    promise的三种状态

    pending(继续) ,resolved(成功)和rejected(失败);

    • pending状态:不会触发thencatch
    • resolved状态: 触发then回调;
    • rejected状态:触发catch回调;
    // 刚定义时,状态默认为 pending
    const p1 = new Promise((resolve, reject) => {
    
    })
    
    // 执行 resolve() 后,状态变成 resolved
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        })
    })
    
    // 执行 reject() 后,状态变成 rejected
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject()
        })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    // 直接返回一个 resolved 状态
    Promise.resolve(100)
    // 直接返回一个 rejected 状态
    Promise.reject('some error')
    
    • 1
    • 2
    • 3
    • 4

    promise的执行;并且不可逆

    • pending --> resolved;成功
    • pending --> rejected;失败

    thencatch改变状态

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

    then catch 会继续返回 Promise ,此时可能会发生状态变化!!!

    // then() 一般正常返回 resolved 状态的 promise
    Promise.resolve().then(() => {
        return 100
    })
    
    // then() 里抛出错误,会返回 rejected 状态的 promise
    Promise.resolve().then(() => {
        throw new Error('err')
    })
    
    // catch() 不抛出错误,会返回 resolved 状态的 promise
    Promise.reject().catch(() => {
        console.error('catch some error')
    })
    
    // catch() 抛出错误,会返回 rejected 状态的 promise
    Promise.reject().catch(() => {
        console.error('catch some error')
        throw new Error('err')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    promise的then/catch题目

    // 第一题
    Promise.resolve().then(() => {
        console.log(1); // 返回的是resolved,所有执行的是后面的then
    }).catch(() => {
        console.log(2);
    }).then(() => {
        console.log(3);
    })
    // 1 3
    // 第二题
    Promise.resolve().then(() => { // 返回 rejected 状态的 promise
        console.log(1)
        throw new Error('erro1'); // 错误Error,执行catch
    }).catch(() => {
        console.log(2)
    }).then(() => {
        console.log(3)
    })
    // 1 2 3
    
    // 第三题
    Promise.resolve().then(() => {// 返回 rejected 状态的 promise
        console.log(1)
        throw new Error('erro1')
    }).catch(() => {
        console.log(2); // 正常的执行resolved的事件
    }).catch(() => { // 注意这里是 catch
        console.log(3)
    })
    // 1 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    async/await

    是一种同步语法彻底消灭异步,是语法糖的形式;用同步的方式编写异步:

    1. 执行async函数,返回的是promise对象;
    2. await相当于promisethen
    3. try...catch可捕获异步,代替了promisecatch

    例如加载图片的同步书写:

    function loadImg(src) {
        const promise = new Promise((resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                reject(new Error(`图片加载失败 ${src}`))
            }
            img.src = src
        })
        return promise
    }
    
    async function loadImg1() {
        const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
        const img1 = await loadImg(src1)
        return img1
    }
    
    async function loadImg2() {
        const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
        const img2 = await loadImg(src2)
        return img2
    }
    
    (async function () {
        // 注意:await 必须放在 async 函数中,否则会报错
        try {
            // 加载第一张图片
            const img1 = await loadImg1()
            console.log(img1)
            // 加载第二张图片
            const img2 = await loadImg2()
            console.log(img2)
        } catch (ex) {
            console.error(ex)
        }
    })()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    async function fn() {
        return 100
    }
    (async function () {
        const a = fn() // ??               // promise
        const b = await fn() // ??         // 100
    })()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    和 Promise 的关系

    • async 函数返回结果都是 Promise 对象(如果函数内没返回 Promise ,则自动封装一下)
    async function fn2() {
        return new Promise(() => {})
    }
    console.log( fn2() )
    async function fn1() {
        return 100
    }
    console.log( fn1() ) // 相当于 Promise.resolve(100)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
    • await 后续跟非 Promise 对象:会直接返回
    (async function () {
        const p1 = new Promise(() => {})
        await p1
        console.log('p1') // 不会执行
    })()
    
    (async function () {
        const p2 = Promise.resolve(100)
        const res = await p2
        console.log(res) // 100
    })()
    
    (async function () {
        const res = await 100
        console.log(res) // 100
    })()
    
    (async function () {
        const p3 = Promise.reject('some err')
        const res = await p3; // 前面是reject
        console.log(res) // 不会执行
    })()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • try...catch捕获rejected 状态
    (async function () {
        const p4 = Promise.reject('some err')
        try {
            const res = await p4
            console.log(res)
        } catch (ex) {
            console.error(ex)
        }
    })()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    总结来看:

    • async 封装 Promise
    • await 处理 Promise 成功
    • try…catch 处理 Promise 失败

    异步本质

    await 是同步写法,但本质还是异步调用。

    async function async1 () {
      console.log('async1 start'); // 2
      await async2()
      console.log('async1 end'); // 5 关键在这一步,它相当于放在 callback 中,最后执行
    }
    async function async2 () {
      console.log('async2'); // 3
    }
    console.log('script start'); // 1
    async1();
    console.log('script end'); // 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    即,只要遇到了 await ,后面的代码都相当于放在 callback 里。

    for…of

    // 定时算乘法
    function multi(num) {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(num * num)
            }, 1000)
        })
    }
    
    // // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
    // function test1 () {
    //     const nums = [1, 2, 3];
    //     nums.forEach(async x => {
    //         const res = await multi(x);
    //         console.log(res);
    //     })
    // }
    // test1();
    
    // 使用 for...of ,可以让计算挨个串行执行
    async function test2 () {
        const nums = [1, 2, 3];
        for (let x of nums) {
            // 在 for...of 循环体的内部,遇到 await 会挨个串行计算
            const res = await multi(x)
            console.log(res)
        }
    }
    test2()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    微任务/宏任务

    • 宏任务:setTimeout setInterval DOM 事件
    • 微任务:Promise(对于前端来说)
    • 微任务比宏任务执行的更早
    console.log(100)
    setTimeout(() => {
        console.log(200)
    })
    Promise.resolve().then(() => {
        console.log(300)
    })
    console.log(400)
    // 100 400 300 200
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    event loop 和 DOM 渲染

    再次回顾 event loop 的过程

    • 每一次 call stack 结束,都会触发 DOM 渲染(不一定非得渲染,就是给一次 DOM 渲染的机会!!!)
    • 然后再进行 event loop
    const $p1 = $('

    一段文字

    '
    ) const $p2 = $('

    一段文字

    '
    ) const $p3 = $('

    一段文字

    '
    ) $('#container') .append($p1) .append($p2) .append($p3) console.log('length', $('#container').children().length ) alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染') // (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果) // 到此,即本次 call stack 结束后(同步任务都执行完了),浏览器会自动触发渲染,不用代码干预 // 另外,按照 event loop 触发 DOM 渲染时机,setTimeout 时 alert ,就能看到 DOM 渲染后的结果了 setTimeout(function () { alert('setTimeout 是在下一次 Call Stack ,就能看到 DOM 渲染出来的结果了') })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    宏任务和微任务的区别

    • 宏任务:DOM 渲染后再触发
    • 微任务:DOM 渲染前会触发
    // 修改 DOM
    const $p1 = $('

    一段文字

    '
    ) const $p2 = $('

    一段文字

    '
    ) const $p3 = $('

    一段文字

    '
    ) $('#container') .append($p1) .append($p2) .append($p3) // // 微任务:渲染之前执行(DOM 结构已更新) // Promise.resolve().then(() => { // const length = $('#container').children().length // alert(`micro task ${length}`) // }) // 宏任务:渲染之后执行(DOM 结构已更新) setTimeout(() => { const length = $('#container').children().length alert(`macro task ${length}`) })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    再深入思考一下:为何两者会有以上区别,一个在渲染前,一个在渲染后?

    • 微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
    • 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
  • 相关阅读:
    UNet网络
    Chapter 2 Gradient Descent
    普通用户使用spark的client无法更新Ranger策略
    Local Light Field Fusion CVPR 2019
    opensbi firmware源码分析(2)
    JS Regex
    CSS3 多个背景图像、线性渐变从上到下(默认)、线性渐变从左到右
    快速入手node.js
    优化思考二
    STM32H750之FreeRTOS学习--------(五)临界段代码保护
  • 原文地址:https://blog.csdn.net/weixin_42369598/article/details/126163904