由於JavaScript是一个单线程的程序语言,这意味着JavaScript一次只能做一件事,虽然只有一个执行绪可以简化程序不用担心并发与冲突的问题,但是这代表JS引擎不能执行长时间的操作,如果一个函数依赖於另一个函数的结果,那麽就必须等待上一个函数结束後才能进行,因此他会将整个主执行绪阻塞导致网页没有反应从而降低使用者体验。
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
let myDate;
for(let i = 0; i < 10000000; i++) {
let date = new Date();
myDate = date
}
console.log(myDate);
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
在电脑能够拥有多个处理器的时代,可以将其他任务放到另一个处理器上处理并让使用者知道何时会完成这样比坐在那等却什麽都没有还来的有意义并可以同时完成其他工作,而这就是Asynchronous的用途了,使用Asynchronous(callback function、promise、async/await...)可以达到执行长时间的网路请求的同时又不会阻塞主执行绪。
在了解非同步JavaScript之前,我们要先了解在JavaScript中是如何运行同步的程序,举个例子:
const second = () => {
console.log('Hello there!');
}
const first = () => {
console.log('Hi there!');
second();
console.log('The End');
}
first(); // Hi there! -> Hello there! -> The End
要了解JavaScript引擎是如何执行上面的程序,我们需要了解Execution Context
与call stack
的概念。
在JavaScript中EC是一个抽象的概念,包含着有关在其中执行当前程序码的环境信息,这句话有点深奥,换句话说当JavaScript运行你的程序码的时候,他会建立一个全域的EC,它包含着这个程序中全域的值、变量、物件、函数,而这些可以被访问的东西就称为环境,而每次运行都只会有一个EC的存在,在JS的Execution Context中主要分为三种类型:
首次加载到浏览器时
会加入,所有的全域程序都会在这里执行。每个函数都有自己的EC
用来包含这个函数中所使用到的变量、物件等等,而FEC可以访问到GEC的程序,而反之亦然。在资料结构中有一个重要的概念LIFO(Last in, First out),而JS中的call stack也是这种资料结构的模式,让我们举个例子:
const second = () => {
console.log('Hello there!');
}
const first = () => {
console.log('Hi there!');
second();
console.log('The End');
}
first();
(图片来源:Understanding Asynchronous JavaScript)
当执行了这个程序时JS引擎会先创造一个GEC (由上图main()表示)并送入call stack中,而当JS引擎发现了first这个函式时,他会为这个函数建立一个FEC并将他放在call stack中。
接下来当执行完console.log('Hi there!')之後将他pop出call stack,之後调用second(),而JS引擎也会为了second建立一个FEC并放在call stack中。
一样的当second中的console.log('Hello there!!')完成後pop出call stack,JS引擎知道second已经执行完毕便将他也pop出stack。
最後将console.log('The end')放入call stack中执行,完成後一样pop出stack,这时first也执行完毕被pop出stack。
当这一系列的操作完成後JS引擎确定没有其他需要执行的程序,便将GEC也pop出call stack代表整个程序完成运行。
在我们讨论到什麽事Event loop之前我们需要了解到为什麽我们需要他,就像我们在前言中提到的,JS是单线程的程序语言,所以当JS做了一件长时间处理的事情时,会造成其他动作被卡住就为了等他完成,这个现象就是所谓的Blocking
,所以为了解决这个问题所以JS才提供了asynchronous这个方法。
const network = () => {
setTimeout(() => {
console.log('Async Code');
}, 2000);
console.log('Hello World');
};
network();
当我们使用setTimeout这个函数,他会将我们的call back function在2秒後再呼叫,所以会先输出Hello World
後在输出Async Code
,对於新手来说可能会觉得疑惑,应该是要过两秒後输出Async Code後在输出Hello World才对啊!接下来我们就要深入介绍到底发生什麽事。
我们先来看一个经典的图:
对於JS来说虽然他本身只有单执行绪,但是由於JS是在浏览器中运行所以能够使用到浏览器的WebAPI,接下来我们来看看对於非同步运行到底发生了什麽事,
(图片来源:所以说event loop到底是什麽玩意儿?| Philip Roberts | JSConf EU)
上面的影片可以充分的解释JS的非同步事件到底发生了什麽事,当JS遇到了非同步事件时,他会将这个事件暂时放在webAPIs中处理,让JS的其他同步事件可以持续进行,而当在webAPIs中处理的事情完成後会将他放在task queue等待call stack中的同步事件完成後,才会透过event loop
将这个非同步事件的结果放回call stack中。
由於queue是FIFO(First in, First out)的资料结构,所以如果有多个非同步事件处理完成,会在task queue中照着完成的进度排队,依序的透过event loop放回call stack中,这就是event loop的概念。
许多人会将parallel
与async
搞混,但是他们是完全不同的概念让我们来仔细将他们厘清。
现在多处理器非常的流行,而多处理器代表机器在同一时间内拥有处理多个事情的能力。
(图片来源:Asynchronous and Parallel Programming)
非同步处理消除了同步处理会造成因为单一线程处理而卡住UI的缺点,可以使用後台运行耗费大量时间的操作,但是对於主要架构来说他依然是只有一个线程,只是将耗时任务委托给後台(其他API)处理 (event loop)。
图片来源:Asynchronous and Parallel Programming)
了解了这两种编译方式的不同後,我们要来介绍对於非同步操作的一些要注意的部分。
var a = 20;
function foo() {
a = a + 1;
}
function bar() {
a = a * 2;
}
// ajax(..) is some arbitrary Ajax function given by a library
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
由於上面的程序对於函数的呼叫都是使用非同步的方式,所以这种情况可能会有两种不同的输出结果,因为对於非同步事件来说都会委托给webAPI进行处理,当处理完成後会将结果放到task queue中等待call stacl任务完成才会通过event loop传给call stack。
但是由於两个非同步的处理完成的时间不一样,先完成的会先被放在task queue中而後完成的则会排在它後面,所以
当foo先完成就会先将a+1所以会输出42,而若bar先完成则会先将a*2输出41,所以当我们使用非同步呼叫函数时,对於同一个变量的操作会因为先後完成顺序的不同而导致有不同的结果有可能会造成非预期的错误,所以在使用非同步呼叫时要额外的小心。
由上面的介绍可以对JavaScript非同步的操作多一点概念,下面我们将整理一下本章的重点:
首次加载到浏览器时
会加入,所有的全域程序都会在这里执行。每个函数都有自己的EC
用来包含这个函数中所使用到的变量、物件等等,而FEC可以访问到GEC的程序,而反之亦然。不同顺序完成会有不同的结果
。You Don't Know JavaScript
ECMAScript® 2020 Language Specification
Execution context, Scope chain and JavaScript internals
Understanding Asynchronous JavaScript
所以说event loop到底是什麽玩意儿?| Philip Roberts | JSConf EU
Asynchronous and Parallel Programming
>>: 认证因素(Authentication Factor)
Command:filter filtertype filterdata filtertype fi...
起初接触Java的时候,正式开始学习写程序的时候,第一支程序通常是『HelloWorld』,学习过J...
本节是以 Golang 上游 ee91bb83198f61aa8f26c3100ca7558d30...
昨天介绍了 before 之後今天就可以直接来看 let 搂! let、let! let =>...
06 - Auto-update dependencies 除了 Release Drafter 及...