【《你不知道的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引擎在编译阶段执行的优化都是安全的优化。

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

上一篇:Nginx实现浏览器端大文件分块上传


下一篇:笔试考试系统--项目搭建及用Log4Net记录日志