JavaScript异步编程--简介

478 阅读4分钟

简介

众所周知javascript是单线程模式,这是因为javascript设计时是被用来运行在浏览器中执行dom操作的,如果设计成多线程模式会出现繁琐的线程通信问题,如:有多个线程同时修改了一处dom,此时需要复杂的运算来决定执行哪个线程的修改。 javascript在调用模式上分为两种

  • 同步模式(Synahronous)
  • 异步模式(Asynahronous)

同步模式

同步模式指的是代码中的任务依次执行,后一个任务要等待前一个任务结束才能执行,执行顺序是按照代码的书写顺序执行,同步模式相对来说要简单些。

示例:

    // 调用栈中添加匿名函数
    
    // 调用栈中压入log
    console.log('begin')  // => begin
    // 调用栈中移除log
    
    function bar() {
        console.log('bar')
    }
    
    function foo() {
        console.log('foo')
        bar()
    }
    
    foo()
    // foo压入调用栈
    // foo中log压入调用栈
    // => bar
    // foo中log被移除调用栈
    // bar压入调用栈
    // bar中log压入调用栈
    // => bar
    // bar中log被移除调用
    // bar被移除调用栈
    // foo被移除调用栈
    
    // log压入调用栈
    console.log('end') // => end
    // log被移除调用栈
    
    // 调用栈清空
    
    // 完整输出 begin => foo => bar => end

同步模式使用起来简单,但是也会出现问题,比如某行代码出现错误,其之后的将不会执行,再比如当某一行代码执行耗时操作时,其后的代码会等待耗时操作完成,这就造成了阻塞(卡死),因此我们还需要异步模式去解决此类问题。

异步模式

不同于同步模式的是不回去等待这个任务的结束才开始下一个任务,对于耗时操作开启后会去执行其他操作,对于耗时操作的后续逻辑一般会通过回调函数的方式解决。异步模式对于javascript很重要,因为没有异步模式单线程的js就无法同事处理大量的耗时任务。

异步模式的难点就是代码执行顺序的混乱,不会那么通俗易懂。

示例:

    console.log('begin')
    
    setTimeout(function timer1() {
        console.log('timer1')
    }, 1500)
    
    setTimeout(function timer2() {
        console.log('timer2')
        setTimeout(function inner() {
            console.log('inner')
        }, 1000)
    }, 1000)
    
    console.log('end')
    
    // 输出
    // begin => end => timer2 => timer1 => inner
    
    // 过程分析
    // begin log 入栈 
    // 输出begin
    // begin log 被移除调用栈
    // timer1 入栈 并开始倒计时
    // timer1 被移除调用栈
    // timer2 入栈 并开始倒计时
    // timer2 被移除调用栈
    // end log 入栈
    // 输出end
    // end log被移除调用栈
    // 调用栈被清空
    // 等到1000ms后timer2会被放入事件队列
    // 事件循环(Event loop)检测到事件队列有任务要处理后将timer2放入调用栈执行
    // 输出timer2 然后从调用栈中移除 同时inner入栈开启倒计时 然后inner被从调用栈移除
    // 等到1500ms后timer1会被放入事件队列
    // 事件循环(Event loop)检测到事件队列有任务要处理后将timer1放入调用栈执行
    // 输出timer1 然后从调用栈中移除
    // 在inner开启倒计时1000ms后inner被放入事件队列
    // 事件循环(Event loop)检测到事件队列有任务要处理后将inner放入调用栈执行
    // 输出inner 然后从调用栈中移除

上边我们只是分析了在同步视野中的执行顺序,其实在真正执行过程中比如timer的调用和开启倒计时是同时执行,因为虽然javascript虽然是单线程的(执行代码),但是浏览器是多线程的(内部的API有单独线程去执行等待的操作,如setTimeout)。

下图展示了工作原理

avatar

回调函数

之前提到过异步模式对javascript语言非常重要,也是javascript的核心特点。由于大量异步API的使用导致了写出的代码没有那么容易读,执行顺序也没那么容易理解。那为了解决此类问题,javascript不断更新API来解决这类问题,也拟补了这方面的不足。

javascript中的异步编程方案其实都是回调函数,回调函数可以理解成你要做的事情,这件事情你需要怎样一步一步往下做,但是并不知道这件事情依赖的任务什么时候完成,所以最好的就是把步骤写到函数中交给异步任务的执行者,这个执行者知道依赖的任务什么时候结束然后去做接下来的事情,这个事情我们就可以理解成回调函数。

一句话总结:由调用者定义,交给执行者执行的函数就是回调函数