"Code tailor" provides technical-related information and a series of basic articles for front-end developers. Follow the "Novices of Xiaoheshan" public account on WeChat to get the latest articles in time.
Preface
Before starting to learn, what we want to tell you is that this article is a summary of the asynchronous operation JavaScript
language knowledge. If you have mastered the following knowledge items, you can skip this section and go directly to the topic exercise
- Single thread
- Synchronization concept
- Asynchronous concept
- Asynchronous operation mode
- Process control of asynchronous operation
- Timer creation and clearing
If you have forgotten some parts, 👇🏻 is ready for you!
Summary
Single thread
Single thread means that JavaScript
only one thread. In other words, JavaScript
can only perform one task at the same time, and other tasks must be queued up later.
JavaScript
reason why 060b5a9c8bfbc8 uses single-threaded instead of multi-threaded is related to history. JavaScript
has been single-threaded since its inception. The reason is that you don't want to make the browser too complicated, because multiple threads need to share resources and may modify each other's running results. This is too complicated for a web scripting language.
single thread benefits :
- Relatively simple to implement
- The execution environment is relatively simple
single-threaded disadvantages :
- The disadvantage is that as long as there is a task that takes a long time, the following tasks must be queued and waited, which will delay the execution of the entire program.
If the queue is due to a large amount of calculations, CPU
busy to be too busy, but that is not the case, but most of the time CPU
is idle because the IO
operation (input and output) is very slow (for example, the Ajax
operation reads data from the network) and has to wait After the results come out, execute it again. JavaScript
language realized that at this time, CPU
can completely ignore the IO
operation, suspend the waiting tasks, and run the tasks that are ranked later. Wait until the IO
operation returns the result, then go back and continue the execution of the suspended task. This mechanism is JavaScript
the internal use of "event loop" mechanism ( Event Loop
).
Although the single thread JavaScript
, it also has advantages that other languages do not have. If used well, the JavaScript
program will not be blocked, which is why Node
can use very few resources to cope with high-traffic access.
In order to take advantage of the computing power CPU
HTML5
proposed the Web Worker
standard, allowing the JavaScript
script to create multiple threads, but the sub-threads are completely controlled by the main thread and cannot operate DOM
. Therefore, this new standard does not change the nature of single-threaded JavaScript
Synchronize
Synchronous behavior corresponds to processor instructions executed sequentially in memory. Each instruction will be executed strictly in the order in which they appear, and the information stored in the system local (such as registers or system memory) can be obtained immediately after each instruction is executed. Such an execution flow is easy to analyze the state of the program when it is executed to any position in the code (such as the value of a variable).
An example of a synchronous operation can be to perform a simple mathematical calculation:
let xhs = 3
xhs = xhs + 4
At each step of the program execution, the state of the program can be inferred. This is because the following instructions are always executed after the previous instructions are completed. When the last designation is completed, xhs
can be used immediately.
First, the operating system allocates a space for storing floating-point values on the stack memory, then performs a mathematical calculation on this value, and then writes the calculation result back to the previously allocated memory. All these instructions are executed sequentially in a single thread. At the level of low-level instructions, there are sufficient tools to determine the state of the system.
asynchronous
Asynchronous behavior is similar to system interrupts, that is, entities outside the current process can trigger code execution. Asynchronous operations are often necessary, because forcing the process to wait for a long operation is usually not feasible (synchronous operations must wait). If the code wants to access some high-latency resources, such as sending a request to a remote server and waiting for a response, there will be a long wait.
An example of asynchronous operation can be to perform a simple mathematical calculation in a timed callback:
let xhs = 3
setTimeout(() => (xhs = xhs + 4), 1000)
This program is ultimately the same as the task executed by the synchronization code. It is to add two numbers together, but this time the thread of execution does not know xhs
value of 060b5a9c8bfdc8 will change, because it depends on when the callback is dequeued from the message queue. carried out.
Asynchronous code is not easy to infer. Although the low-level code corresponding to this example is ultimately the same as the previous example, the second instruction block (add operation and assignment operation) is triggered by the system timer, which generates an interrupt for enqueue execution. When exactly will this interrupt be triggered, this JavaScript
, so it is actually unpredictable (although it can be guaranteed that this will happen after the synchronization code of the current thread is executed, otherwise the callback will not have a chance to be executed out of queue) . In any case, there is basically no way to know when the system state changes after the callback is scheduled.
In order for subsequent codes to use xhs
, the asynchronously executed function needs to notify other codes after xhs
If the program does not need this value, then just continue execution without waiting for the result.
Task queue and event loop
When JavaScript is running, in addition to a running main thread, the engine also provides a task queue ( task queue
), which contains various asynchronous tasks that need to be processed by the current program. (Actually, there are multiple task queues depending on the type of asynchronous tasks. For ease of understanding, it is assumed that there is only one queue.)
First, the main thread will perform all synchronization tasks. When all the synchronous tasks are executed, they will look at the asynchronous tasks in the task queue. If the conditions are met, the asynchronous task will re-enter the main thread to start execution, and then it will become a synchronous task. After the execution is complete, the next asynchronous task enters the main thread to start execution. Once the task queue is emptied, the program ends execution.
Asynchronous tasks are usually written as callback functions. Once the asynchronous task re-enters the main thread, the corresponding callback function will be executed. If an asynchronous task does not have a callback function, it will not enter the task queue, that is, it will not re-enter the main thread, because the callback function is not used to specify the next operation.
JavaScript
engine know whether the asynchronous task has a result, and can it enter the main thread? The answer is that the engine is constantly checking, over and over again, as long as the synchronization tasks are executed, the engine will check whether the suspended asynchronous tasks can enter the main thread. This kind of loop checking mechanism is called event loop ( Event Loop
).
Wikipedia is defined as: "The event loop is a program structure used to wait and send messages and events (a programming construct that waits for and dispatches events or messages in a program)".
Asynchronous operation mode
Callback
Callback function is the most basic method of asynchronous operation.
The following are two functions f1
and f2
. The programming intention is that f2
must wait until f1
executed before it can be executed.
function f1() {
// ...
}
function f2() {
// ...
}
f1()
f2()
The problem with the above code is that if f1
is an asynchronous operation, f2
will be executed immediately, and will not wait until f1
over.
At this time, you can consider rewriting f1
, and write f2
as the callback function of f1
function f1(callback) {
// ...
callback()
}
function f2() {
// ...
}
f1(f2)
The advantage of the callback function is that it is simple, easy to understand and implement. The disadvantage is that it is not conducive to the reading and maintenance of the code. The height between the various parts is coupled with ( coupling
), which makes the program structure chaotic and the process difficult to trace (especially multiple callback function embedded Set the situation), and each task can only specify a callback function.
Process control of asynchronous operation
If there are multiple asynchronous operations, there is a process control problem: how to determine the order in which asynchronous operations are executed, and how to ensure that this order is adhered to.
function async(arg, callback) {
console.log('参数为 ' + arg + ' , 1秒后返回结果')
setTimeout(function () {
callback(arg * 2)
}, 1000)
}
async
function of the above code is an asynchronous task, which is very time-consuming. Each execution takes 1
seconds to complete, and then the callback function is called.
If there are six such asynchronous tasks, they need to be completed before the final final
function can be executed. How should the operation process be arranged?
function final(value) {
console.log('完成: ', value)
}
async(1, function (value) {
async(2, function (value) {
async(3, function (value) {
async(4, function (value) {
async(5, function (value) {
async(6, final)
})
})
})
})
})
// 参数为 1 , 1秒后返回结果
// 参数为 2 , 1秒后返回结果
// 参数为 3 , 1秒后返回结果
// 参数为 4 , 1秒后返回结果
// 参数为 5 , 1秒后返回结果
// 参数为 6 , 1秒后返回结果
// 完成: 12
In the above code, the nesting of the six callback functions is not only troublesome to write, error-prone, and difficult to maintain.
Serial execution
We can write a flow control function, let it control asynchronous tasks, after one task is completed, then execute another. This is called serial execution.
var items = [1, 2, 3, 4, 5, 6]
var results = []
function async(arg, callback) {
console.log('参数为 ' + arg + ' , 1秒后返回结果')
setTimeout(function () {
callback(arg * 2)
}, 1000)
}
function final(value) {
console.log('完成: ', value)
}
function series(item) {
if (item) {
async(item, function (result) {
results.push(result)
return series(items.shift())
})
} else {
return final(results[results.length - 1])
}
}
series(items.shift())
In the above code, the function series
is a serial function. It will execute asynchronous tasks in sequence. After all tasks are completed, the final
function will be executed. items
array holds the parameters of each asynchronous task, and the results
array holds the running result of each asynchronous task.
Note that the above writing takes six seconds to complete the entire script.
Parallel execution
The flow control function can also be executed in parallel, that is, all asynchronous tasks are executed at the same time, and the final
function is executed after all the tasks are completed.
var items = [1, 2, 3, 4, 5, 6]
var results = []
function async(arg, callback) {
console.log('参数为 ' + arg + ' , 1秒后返回结果')
setTimeout(function () {
callback(arg * 2)
}, 1000)
}
function final(value) {
console.log('完成: ', value)
}
items.forEach(function (item) {
async(item, function (result) {
results.push(result)
if (results.length === items.length) {
final(results[results.length - 1])
}
})
})
In the above code, the forEach
method will initiate six asynchronous tasks at the same time. final
In comparison, the above writing takes only one second to complete the entire script. This means that parallel execution is more efficient and saves time compared to serial execution which can only execute one task at a time. But the problem is that if there are many parallel tasks, it is easy to exhaust system resources and slow down the running speed. So there is a third method of process control.
Combination of parallel and serial
The so-called combination of parallel and serial is to set a threshold, at most n
asynchronous tasks can be executed in parallel each time, so as to avoid excessive occupation of system resources.
var items = [1, 2, 3, 4, 5, 6]
var results = []
var running = 0
var limit = 2
function async(arg, callback) {
console.log('参数为 ' + arg + ' , 1秒后返回结果')
setTimeout(function () {
callback(arg * 2)
}, 1000)
}
function final(value) {
console.log('完成: ', value)
}
function launcher() {
while (running < limit && items.length > 0) {
var item = items.shift()
async(item, function (result) {
results.push(result)
running--
if (items.length > 0) {
launcher()
} else if (running == 0) {
final(results)
}
})
running++
}
}
launcher()
In the above code, at most two asynchronous tasks can be run at the same time. The variable running
records the number of tasks currently running. As long as it is below the threshold, a new task will be started. If it is equal to 0
, it means that all tasks have been executed. At this time, the final
function will be executed.
This code takes three seconds to complete the entire script, between serial execution and parallel execution. By adjusting the limit
, the best balance of efficiency and resources can be achieved.
Timer creation and clearing
JavaScript
is executed in a single thread in the browser, but allows the use of timers to specify that the corresponding code will be executed after a certain time or at regular intervals. setTimeout()
used to specify the execution of certain codes after a certain period of time, and setInterval()
used to specify the execution of certain codes at regular intervals.
setTimeout()
method usually receives two parameters: the code to be executed and the time (in milliseconds) to wait before executing the callback function. The first parameter can be JavaScript
(similar to eval()
) or a function.
// 在 1 秒后显示警告框
setTimeout(() => alert('Hello XHS-Rookies!'), 1000)
The second parameter is the number of milliseconds to wait, not the exact time to execute the code. JavaScript
is single-threaded, so only one piece of code can be executed at a time. In order to schedule the execution of different codes, JavaScript
maintains a task queue. The tasks will be executed in the order in which they are added to the queue. setTimeout()
the second parameter just tell JavaScript
engine after a specified number of milliseconds to add tasks to this queue. If the queue is empty, the code will be executed immediately. If the queue is not empty, the code must wait for the execution of the previous task to be executed.
When calling setTimeout()
, it will return a value ID
that represents the timeout schedule. This timeout ID
is a unique identifier of the code scheduled for execution and can be used to cancel the task. To cancel the waiting scheduled task, you can call the clearTimeout()
method and pass in the timeout ID
, as shown in the following example:
// 设置超时任务
let timeoutId = setTimeout(() => alert('Hello XHS-Rookies!'), 1000)// 取消超时任务clearTimeout(timeoutId)
clearTimeout()
is called before the specified time arrives, the timeout task can be cancelled. clearTimeout()
after the task is executed has no effect.
Note that all code execution timeout (function) runs in an anonymous function in the global scope, so the function ofthis
value in non-strict mode always pointwindow
, and is in strict modeundefined
. If an arrow function is providedsetTimeout()
this
will remain in the vocabulary scope in which it was defined.
setInterval()
of setTimeout()
is similar to 060b5a9c8c026d, except that the specified task will be executed every specified time until the cycle timing is cancelled or the page is unloaded. setInterval()
can also receive two parameters: the code to be executed (string or function), and the time (in milliseconds) to wait for the next task of executing the timing code to be added to the queue. Below is an example:
setInterval(() => alert('Hello XHS-Rookies!'), 10000)
Note that the key point here is that the second parameter, the interval time, refers to the time to wait before adding a new task to the queue. For example, the time to setInterval()
01:00:00
, and the interval time is 3000
milliseconds. This means that 01:00:03
, the browser will add the task to the execution queue. The browser does not care when this task is executed or how long it takes to execute. Therefore, at 01:00:06
, it will add another task to the queue. It can be seen that the callback function with short execution time and non-blocking is more suitable for setInterval()
.
setInterval()
method will also return a loop timing ID
, which can be used to cancel the loop timing at a certain point in the future. To cancel the loop timing, you can call clearInterval()
and pass in the timing ID
. Compared to setTimeout()
, the ability to cancel timing is more important setInterval()
After all, if you leave it alone, the timed task will be executed until the page is unloaded. Here is a common example:
let xhsNum = 0,
intervalId = null
let xhsMax = 10
let xhsIncrementNumber = function () {
xhsNum++
// 如果达到最大值,则取消所有未执行的任务
if (xhsNum == xhsMax) {
clearInterval(xhsIntervalId) // 清除定时器
alert('Done')
}
}
xhsIntervalId = setInterval(xhsIncrementNumber, 500)
In this example, the variable num
will increase every half a second until it reaches the maximum limit. At this time, the cycle timing will be cancelled. This mode can also be setTimeout()
, such as:
let xhsNum = 0
let xhsMax = 10
let xhsIncrementNumber = function () {
xhsNum++
// 如果还没有达到最大值,再设置一个超时任务
if (xhsNum < xhsMax) {
setTimeout(xhsIncrementNumber, 500)
} else {
alert('Done')
}
}
setTimeout(xhsIncrementNumber, 500)
Note that when usingsetTimeout()
, it is not necessary to record the timeoutID
, because it will automatically stop when the conditions are met, otherwise it will automatically set another timeout task. This mode is the recommended way to set up recurring tasks.setIntervale()
is rarely used in a production environment in practice, because the time interval between the end of a task and the beginning of the next task cannot be guaranteed, and some cyclic timing tasks may be skipped as a result.setTimeout()
as in the previous example ensures that this will not happen. Generally speaking, it is best not to usesetInterval()
.
Self-test
1. What is the output of the following code?
console.log('first')
setTimeOut(() => {
console.log('second')
}, 1000)
console.log('third')
2. Make a 60s timer.
Problem analysis
One,
// first
// third
// second
setTimeOut
causes the contents to enter the asynchronous queue when executed. So will first perform the following third
after the output, only the output setTimeOut
content.
two,
function XhsTimer() {
var xhsTime = 60 // 设置倒计时时间 60s
const xhsTimer = setInterval(() => {
// 创建定时器
if (xhsTime > 0) {
// 大于 0 时,一次次减
xhsTime--
console.log(xhsTime) // 输出每一秒
} else {
clearInterval(xhsTimer) // 清除定时器
xhsTime = 60 // 重新设置倒计时时间 60s
}
}, 1000) // 1000 为设置的时间,1000毫秒 也就是一秒
}
XhsTimer()
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。