• JavaScript中的异步、同步


    要理解JS中的异步、同步,需要先了解JS代码的执行过程和Event Loop。

    JavaScript代码的执行过程

    程序需要执行的操作都会被放入Call Stack(A LIFO (Last In, First Out) Stack),先进后出的数据结构。

    const bar = () => console.log('bar')
    
    const baz = () => console.log('baz')
    
    const foo = () => {
      console.log('foo')
      bar()
      baz()
    }
    
    foo()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    当这段代码执行时,foo()会首先被调用。在foo()内部先调用了bar(),然后调用了baz()。在这个时刻Call Stack 是下面的样子:
    图例

    执行每一步操作都有新的操作压入栈顶,执行完毕就从栈顶弹出,直到整个栈变为Empty。

    Event Loop 起到的作用就是每次迭代都回去检查Call Stack中是否有待执行的指令,然后去执行它。

    JavaScript中的异步、同步

    同步执行代码

    大多数的情况下,JavaScript是以同步的形式执行代码:

    let log = console.log;
    
    let a = 5;
    let b = 50;
    
    let a1 = function() { return 5 }
    let b1 = function() { return 50 }
    
    log(a1())
    log(a2())
    
    let a2 = function(num) { return 5*num }
    let b2 = function() { return 50 }
    
    log(a2(b2))
    // 打印出:
    // 5
    // 50
    // 250
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    异步执行的代码

    setTimeout, callbacks, Promise, fetch, ajax, filesystem interaction, database calls, DOM event listener

    上边这些情况代码将是异步执行。

    原因是代码执行到这些方法时,是不确定对应的操作多久可以执行完毕,所以会继续向下执行。

    考虑下面的情形:

    let a3 = 100;
    setTimeout(function() { a3++ }, 0);
    log(a3)
    setTimeout(function() { log(a3) }, 0);
    // 打印出
    // 100
    // 101
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    同步形式的代码被放入 Call Stack 中执行,异步代码放到了一个队列中(Message Queue)。Event Loop 会优先处理 Call Stack 中的任务,当 Call Stack 为空,就会把 Message Queue 中的任务拿出来执行。

    ES6 Job Queue

    ECMAScript 2015 引入了 Job Queue 的概念,被 Promises 使用。它会使异步方法的结果尽快执行,而不是放到 Call Stack 的最后执行。

    Promise是异步的一个非常好的实现:

    const bar = () => console.log('bar')
    
    const baz = () => console.log('baz')
    
    const foo = () => {
      console.log('foo')
      setTimeout(bar, 0)
      new Promise((resolve, reject) =>
        resolve('should be right after baz, before bar')
      ).then(resolve => console.log(resolve))
      baz()
    }
    
    foo()
    
    // foo
    // baz
    // should be right after baz, before bar
    // bar
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    可以看到Promise会先于setTimeout执行。

    看一下这段代码,你判断一下它的执行顺序是怎么样的?

    console.log(1);
    
    setTimeout(() => console.log(2));
    
    Promise.resolve().then(() => console.log(3));
    
    Promise.resolve().then(() => setTimeout(() => console.log(4)));
    
    Promise.resolve().then(() => console.log(5));
    
    setTimeout(() => console.log(6));
    
    console.log(7);
    // 1
    // 7
    // 3
    // 5
    // 2
    // 6
    // 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    Nodejs Process.NextTick() 和 SetImmediate()

    Process.NextTick() 会在事件循环的一个周期的最后执行,通过这个方法可以实现,把一个异步方法尽快执行而不是放入异步队列中。

    Process.NextTick 回调函数会被添加到 Process.NextTick 队列,Promise.Then() 会被添加到 Promises 微任务队列(Microtask Queue),SetTimeout, SetImmediate 会被添加到宏任务队列(Macrotask Queue)。

    SetTimeout() 延时0ms的异步与 SetImmediate()很相似,它们都是在下一次事件循环执行。

    事件循环会先执行 Process.NextTick 队列,然后执行 Promises 微任务队列,然后是 宏任务队列。

    console.log('script start');
    
    // 异步
    Promise.resolve().then(function() {
      console.log('promise');
    }).then(function() {
      console.log('promise-then');
    });
    
    // 异步
    setImmediate(function() {
        console.log('setImmediate')
    })
    
    // 异步
    setTimeout(function() {
        console.log('setTimeout 0')
    }, 0)
    
    // 异步
    setTimeout(function() {
        return new Promise(resolve => {
            console.log('setTimeout-delay 100ms promise')
            resolve()
        }).then(res => {
            console.log('setTimeout-delay 100ms promise.then')
        })
    }, 100)
    
    process.nextTick(function() {
        console.log('process.nextTick')
    })
    
    console.log('script end');
    
    /*
    script start
    script end
    process.nextTick
    promise
    promise-then
    setTimeout 0
    setImmediate
    setTimeout-delay 100ms promise
    setTimeout-delay 100ms promise.then
    */
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    参考链接

    https://nodejs.dev/en/learn/the-nodejs-event-loop/

    文章首发于 IICOOM-个人博客|技术博客 《JavaScript中的异步、同步》

  • 相关阅读:
    操作系统笔记(1)- 计算机硬件介绍
    Redis 用户管理手册
    ubuntu22.04备份系统的完整操作过程
    基础算法练习200题07、编框
    nodejs 简介
    认识Vue扩展插件
    opencv-phase 函数
    【讲座笔记】基于 Apache Calcite 的多引擎指标管理最佳实践|CommunityOverCode Asia 2023 | 字节开源
    第3周学习:ResNet+ResNeXt
    un8.15:SpringBoot——集成MyBatis+SpringMVC
  • 原文地址:https://blog.csdn.net/IICOOM/article/details/126726091