


Analyzing callbacks and code design patterns in Node.js asynchronous programming_node.js
The biggest selling point of NodeJS - event mechanism and asynchronous IO, are not transparent to developers. Developers need to write code asynchronously to take advantage of this selling point, which has been criticized by some NodeJS opponents. But no matter what, asynchronous programming is indeed the biggest feature of NodeJS. Without mastering asynchronous programming, you cannot say that you have truly learned NodeJS. This chapter will introduce various knowledge related to asynchronous programming.
In code, the direct manifestation of asynchronous programming is callbacks. Asynchronous programming relies on callbacks, but it cannot be said that the program becomes asynchronous after using callbacks. We can first look at the following code.
function heavyCompute(n, callback) { var count = 0, i, j; for (i = n; i > 0; --i) { for (j = n; j > 0; --j) { count += 1; } } callback(count); } heavyCompute(10000, function (count) { console.log(count); }); console.log('hello');
100000000 hello
As you can see, the callback function in the above code is still executed before subsequent code. JS itself runs in a single thread, and it is impossible to run other code before a piece of code has finished running, so there is no concept of asynchronous execution.
However, if what a function does is to create another thread or process, do something in parallel with the JS main thread, and notify the JS main thread when the thing is done, then the situation is different. Let's take a look at the following code.
setTimeout(function () { console.log('world'); }, 1000); console.log('hello');
hello world
This time you can see that the callback function is executed after the subsequent code. As mentioned above, JS itself is single-threaded and cannot be executed asynchronously. Therefore, we can think that what special functions provided by the running environment outside of JS specifications such as setTimeout do is to create a parallel thread and return immediately, allowing the JS master to The process can then execute subsequent code and execute the callback function after receiving notification from the parallel process. In addition to the common ones such as setTimeout and setInterval, such functions also include asynchronous APIs provided by NodeJS such as fs.readFile.
In addition, we still return to the fact that JS runs in a single thread, which determines that JS cannot execute other code, including callback functions, before executing a piece of code. In other words, even if the parallel thread completes its work and notifies the JS main thread to execute the callback function, the callback function will not start execution until the JS main thread is idle. The following is such an example.
function heavyCompute(n) { var count = 0, i, j; for (i = n; i > 0; --i) { for (j = n; j > 0; --j) { count += 1; } } } var t = new Date(); setTimeout(function () { console.log(new Date() - t); }, 1000); heavyCompute(50000);
8520
As you can see, the actual execution time of the callback function that was supposed to be called after 1 second was greatly delayed because the JS main thread was busy running other code.
Code Design Patterns
Asynchronous programming has many unique code design patterns. In order to achieve the same function, the code written in synchronous mode and asynchronous mode will be very different. Some common patterns are introduced below.
Function return value
It is a very common requirement to use the output of one function as the input of another function. In synchronous mode, the code is generally written as follows:
var output = fn1(fn2('input')); // Do something.
In asynchronous mode, since the function execution result is not passed through the return value, but through the callback function, the code is generally written in the following way:
fn2('input', function (output2) { fn1(output2, function (output1) { // Do something. }); });
As you can see, this method is one callback function nested within one callback function. If there are too many, it is easy to write >-shaped code.
Traverse the array
When traversing an array, it is also a common requirement to use a function to perform some processing on the data members in sequence. If the function is executed synchronously, the following code will generally be written:
var len = arr.length, i = 0; for (; i < len; ++i) { arr[i] = sync(arr[i]); } // All array items have processed.
If the function is executed asynchronously, the above code cannot guarantee that all array members have been processed after the loop ends. If array members must be processed serially one after another, asynchronous code is generally written as follows:
(function next(i, len, callback) { if (i < len) { async(arr[i], function (value) { arr[i] = value; next(i + 1, len, callback); }); } else { callback(); } }(0, arr.length, function () { // All array items have processed. }));
As you can see, the above code only passes in the next array member and starts the next round of execution after the asynchronous function is executed once and returns the execution result. Until all array members are processed, the execution of subsequent code is triggered through callbacks. .
If array members can be processed in parallel, but subsequent code still requires all array members to be processed before they can be executed, the asynchronous code will be adjusted to the following form:
(function (i, len, count, callback) { for (; i < len; ++i) { (function (i) { async(arr[i], function (value) { arr[i] = value; if (++count === len) { callback(); } }); }(i)); } }(0, arr.length, 0, function () { // All array items have processed. }));
As you can see, compared with the asynchronous serial traversal version, the above code processes all array members in parallel, and uses the counter variable to determine when all array members have been processed.
Exception handling
The exception catching and handling mechanism provided by JS itself - try..catch.., can only be used for synchronously executed code. Below is an example.
function sync(fn) { return fn(); } try { sync(null); // Do something. } catch (err) { console.log('Error: %s', err.message); }
Error: object is not a function
As you can see, the exception will bubble along the code execution path until it is caught when it encounters the first try statement. However, since asynchronous functions interrupt the code execution path, when exceptions generated during and after the execution of the asynchronous function bubble up to the location where the execution path is interrupted, if no try statement is encountered, they will be thrown as a global exception. Below is an example.
function async(fn, callback) { // Code execution path breaks here. setTimeout(function () { callback(fn()); }, 0); } try { async(null, function (data) { // Do something. }); } catch (err) { console.log('Error: %s', err.message); }
/home/user/test.js:4 callback(fn()); ^ TypeError: object is not a function at null._onTimeout (/home/user/test.js:4:13) at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
因为代码执行路径被打断了,我们就需要在异常冒泡到断点之前用 try 语句把异常捕获住,并通过回调函数传递被捕获的异常。于是我们可以像下边这样改造上边的例子。
function async(fn, callback) { // Code execution path breaks here. setTimeout(function () { try { callback(null, fn()); } catch (err) { callback(err); } }, 0); } async(null, function (err, data) { if (err) { console.log('Error: %s', err.message); } else { // Do something. } });
Error: object is not a function
可以看到,异常再次被捕获住了。在 NodeJS 中,几乎所有异步 API 都按照以上方式设计,回调函数中第一个参数都是 err。因此我们在编写自己的异步函数时,也可以按照这种方式来处理异常,与 NodeJS 的设计风格保持一致。
有了异常处理方式后,我们接着可以想一想一般我们是怎么写代码的。基本上,我们的代码都是做一些事情,然后调用一个函数,然后再做一些事情,然后再调用一个函数,如此循环。如果我们写的是同步代码,只需要在代码入口点写一个 try 语句就能捕获所有冒泡上来的异常,示例如下。
function main() { // Do something. syncA(); // Do something. syncB(); // Do something. syncC(); } try { main(); } catch (err) { // Deal with exception. }
但是,如果我们写的是异步代码,就只有呵呵了。由于每次异步函数调用都会打断代码执行路径,只能通过回调函数来传递异常,于是我们就需要在每个回调函数里判断是否有异常发生,于是只用三次异步函数调用,就会产生下边这种代码。
function main(callback) { // Do something. asyncA(function (err, data) { if (err) { callback(err); } else { // Do something asyncB(function (err, data) { if (err) { callback(err); } else { // Do something asyncC(function (err, data) { if (err) { callback(err); } else { // Do something callback(null); } }); } }); } }); } main(function (err) { if (err) { // Deal with exception. } });
可以看到,回调函数已经让代码变得复杂了,而异步方式下对异常的处理更加剧了代码的复杂度。

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

How to handle file upload? The following article will introduce to you how to use express to handle file uploads in the node project. I hope it will be helpful to you!

This article will share with you Node's process management tool "pm2", and talk about why pm2 is needed, how to install and use pm2, I hope it will be helpful to everyone!

Detailed explanation and installation guide for PiNetwork nodes This article will introduce the PiNetwork ecosystem in detail - Pi nodes, a key role in the PiNetwork ecosystem, and provide complete steps for installation and configuration. After the launch of the PiNetwork blockchain test network, Pi nodes have become an important part of many pioneers actively participating in the testing, preparing for the upcoming main network release. If you don’t know PiNetwork yet, please refer to what is Picoin? What is the price for listing? Pi usage, mining and security analysis. What is PiNetwork? The PiNetwork project started in 2019 and owns its exclusive cryptocurrency Pi Coin. The project aims to create a one that everyone can participate

Authentication is one of the most important parts of any web application. This tutorial discusses token-based authentication systems and how they differ from traditional login systems. By the end of this tutorial, you will see a fully working demo written in Angular and Node.js. Traditional Authentication Systems Before moving on to token-based authentication systems, let’s take a look at traditional authentication systems. The user provides their username and password in the login form and clicks Login. After making the request, authenticate the user on the backend by querying the database. If the request is valid, a session is created using the user information obtained from the database, and the session information is returned in the response header so that the session ID is stored in the browser. Provides access to applications subject to

Quick Application: Practical Development Case Analysis of PHP Asynchronous HTTP Download of Multiple Files With the development of the Internet, the file download function has become one of the basic needs of many websites and applications. For scenarios where multiple files need to be downloaded at the same time, the traditional synchronous download method is often inefficient and time-consuming. For this reason, using PHP to download multiple files asynchronously over HTTP has become an increasingly common solution. This article will analyze in detail how to use PHP asynchronous HTTP through an actual development case.

As the volume of Internet business continues to grow, the demand for high concurrency and high performance is getting higher and higher, and Swoole, as a network communication framework for PHP, is increasingly favored by developers. Among them, Swoole supports asynchronous AMQP, which is one of the more common application scenarios. So let's take a look at how Swoole supports asynchronous AMQP operations. First, we need to clarify what AMQP is. AMQP (AdvancedMessageQueuingProtocol) Advanced

With the continuous development and popularization of the Internet, email has become an indispensable part of people's lives and work, and SMTP (Simple Mail Transfer Protocol) is one of the important protocols for email sending. As an asynchronous network communication framework for PHP, Swoole can well support asynchronous SMTP operations, making email sending more efficient and stable. This article will introduce how Swoole supports asynchronous SMTP operations, including using sync

Concurrent and Asynchronous Programming Concurrent programming deals with multiple tasks executing simultaneously, asynchronous programming is a type of concurrent programming in which tasks do not block threads. asyncio is a library for asynchronous programming in python, which allows programs to perform I/O operations without blocking the main thread. Event loop The core of asyncio is the event loop, which monitors I/O events and schedules corresponding tasks. When a coroutine is ready, the event loop executes it until it waits for I/O operations. It then pauses the coroutine and continues executing other coroutines. Coroutines Coroutines are functions that can pause and resume execution. The asyncdef keyword is used to create coroutines. The coroutine uses the await keyword to wait for the I/O operation to complete. The following basics of asyncio
