• 【《你不知道的JS(中卷②)》】一、 异步:现在与未来


    一、异步:现在与未来:

    如何表达和控制持续一段时间的程序行为,是使用类似JS这样的语言编程时,很重要但常常被误解的一点。

    持续一段时间,不是指类似于 for循环开始到结束的过程。而是指 程序的一部分现在运行,而另一部分则在未来运行。现在与将来之间有一段间隙,这段间隙在实际程序中,可以是等待用户输入、从数据库或文件系统中请求数据、通过网络发送数据并等待响应,或者是在以固定时间间隔执行重复任务(比如动画)。

    ​ 管理这段间隙,就是异步编程的核心。本章将深入探讨异步的概念及其在JS中的运作模式。

    一)、分块的程序:

    ​ 无论JS程序是在多个JS文件或一个JS文件,JS中都是都是由一个个块组成。同一时间只有一个块可以在 现在执行。最常见的块就是 函数

    ​ 例如:

    var data = ajax("http://some.url.1");
    console.log(data);  // data通常并不会出现Ajax的结果
    

    ​ 因为标准Ajax请求不是同步完成的,当打印data时,ajax函数可能还没有返回任何值给变量data。

    ​ 如何能够确定得到ajax函数的返回值?1、将ajax(...)能够阻塞到响应返回,即发出ajax请求后什么事也不做,直到得到返回值。这与我们希望将一部分值在 未来运行是相违背的。

    现在我们发出一个异步Ajax请求,然后在将来才能得到返回的结果。实现“等待”的最简单方法,是使用 回调函数

    ajax("http://some.url.1", function myCallbackFunction(data){
        console.log(data);  // 这里得到数据
    })
    

    ​ 另外,

    function now() {
        return 21;
    }
    
    function later() {
        answer = answer * 2;
        console.log("Meaning of life:", answer);
    }
    
    var answer = now();
    
    setTimeout(later, 1000);  // Meaning of life: 42
    

    任何时候,只要把一段代码包装称一个函数,并指定它在响应某个时间(定时器、鼠标点击、Ajax响应等)时执行,你就在代码中创建了一个将来执行的块,也就在这个程序中引入了异步机制*。

    • 不同的浏览器和JS环境可能有不同的控制台异步实现。

    二)、事件循环:

    ​ JS引擎并不是独立运行的,它运行在宿主环境中,大多数为Web浏览器,也有如Node.js等服务器端环境。

    ​ 这些环境提供一种机制来处理程序中多个块的执行,且执行每块时调用JS引擎,这种机制被称为 事件循环。 可以按照下面的伪代码来理解事件循环:

    var eventLoop = [];
    var event;
    
    //“永远”执行
    while (true) {
        // 一次tick
        if (eventLoop.length > 0) {
            event = eventLoop.shift();
            try {
                event();
            }
            catch (err) {
                reportError(err);
            }
        }
    }
    

    setTimeout(..)无法将回调函数直接挂在事件循环队列中,只能通过定时器,让环境将回调函数放进去。因此setTimeout(..)方法精度不是很高。

    三)、并行线程:

    ”异步“与”并行“常常被混为一谈,但实际上它们的意义完全不同。

    • 异步:关于现在将来的时间间隙。

      事件循环把自身的工作分成一个个任务并顺序执行,不允许对共享内存并行访问和修改。通过分立线程中彼此合作的事件循环,并行和顺序执行可以共存。

    • 并行:关于能够同时发生的事情。

      并行最常见的工具有进程线程。进程与线程独立运行,并可能同时运行,多个线程能够共享单个进程的内存。

    • JS不支持跨线程共享数据,并且具有 完整运行特性,即如果有两个函数执行,两个函数不会交替运行,一定是先完整执行第一个函数,然后才是第二个函数。

      虽然JS不需要考虑 线程层次的不确定性,但是依然存在 竞态条件,考虑下面的代码:

    var a = 1;
    var b = 2;
    
    function foo() {
        a++;
        b = b * a;
        a = b + 3;
    }
    
    function bar() {
        b--;
        a = 8 + b;
        b = a * 2;
    }
    
    ajax("http;//some.url.1", foo);
    ajax("http;//some.url.2", bar);
    

    ​ 这一段代码可能有很多的输出,这种不确定性来自于 函数(事件)顺序级别上,换句话说,这种不确定性低于多线程情况

    四)、并发:

    ​ 设想一个状态更新列表,随着用户向下滚动列表而住就按加载更多内容。这里需要两个”进程“,一个监听页面滚动触发onscroll,并发起Ajax请求,另一个接受Ajax请求,并把内容展示到页面。当用户滚动页面时,可能在等待第一个响应的同时,就会有第二、第三个请求发出。

    ​ 两个或多个”进程“同时执行就出现了并发,不管组成它们的单个运算是否 并行执行(在独立的处理器或处理器核心上同时运行)。可以把并发看作”进程级的并行,与运算级的并行(不同处理器上的线程)相对。

    • 如果并发的”进程“需要通过作用域DOM间接监护,就需要对交互进行协调,避免竞态的出现。

    五)、任务:

    ​ 在ES6中,有一个新的概念建立在事件循环队列之上,叫做任务队列(job queue),这个概念用于Promise的异步特性。

    ​ 任务队列是挂在前文提到的事件循环队列的每个tick之后的一个队列,但是不是被添加到队列末尾,而可以直接插队,优先处理,也即:”尽可能早的将来“

    六)、语句顺序:

    ​ 代码中语句的顺序和JS引擎执行语句的顺序并不一定要一致。JS引擎在编译代码之后,可能会对语句的顺序进行重新安排,以提高执行速度。可以保证的是,JS引擎在编译阶段执行的优化都是安全的优化。

  • 相关阅读:
    《企业虚拟化应用实战》笔记
    s3c2440笔记1(启动)
    reactor & proactor 笔记
    交换机选型笔记
    Intel VT-x 基本概念
    note of introduction of Algorithms(Lecture 3
    STC12C5A60S2笔记8(串口)
    STC12C5A60S2笔记7(定时器)
    bzoj 3242: [Noi2013]快餐店 章鱼图
    高精度模板
  • 原文地址:https://www.cnblogs.com/enmac/p/13306197.html
Copyright © 2020-2023  润新知