JavaScript 异步:是什么以及如何使用它
在编写javascript应用程序时,您可能会遇到异步函数,例如浏览器中的fetch函数或nodejs中的readfile函数。
如果您像通常一样使用这些函数之一,可能会得到意外的结果。这是因为它们是异步函数。本文指导您了解异步函数的含义以及如何像专业人士一样使用异步函数。
同步函数简介
javascript是一种单线程语言,一次只能做一件事。这意味着如果处理器遇到需要很长时间的函数,javascript会等待整个函数执行完毕后再继续执行程序的其他部分。
大多数函数由处理器完全执行。这意味着在执行这些函数期间,无论花费多长时间,处理器都将完全忙碌。这些被称为同步函数。下面定义了一个示例同步函数:
function add(a, b) {
for (let i = 0; i < 1000000; i ++) {
// 什么都不做
}
return a + b;
}
// 调用该函数需要很长时间
sum = add(10, 5);
// 然而,处理器无法进入下面的代码
console.log(sum);
这个函数执行了一个大循环,执行时间很长,然后返回其两个参数的和。
在定义完函数后,我们调用了它,并将其结果存储在sum变量中。接下来,我们记录了sum变量的值。即使执行add函数需要一些时间,但处理器在执行完成之前无法继续记录sum的值。
您将遇到的绝大多数函数都会像上面的函数一样按照可预测的方式运行。但是,有些函数是异步的,不像常规函数那样工作。
异步函数简介
异步函数在处理器外部执行大部分工作。这意味着即使函数的执行需要一些时间,处理器也会空闲下来,可以做更多的工作。
以下是这样一个函数的示例:
fetch('https://jsonplaceholder.typicode.com/users/1');
为了提高效率,javascript使处理器能够在异步函数的执行完成之前继续执行需要cpu的其他任务。
由于处理器在异步函数的执行完成之前已经继续执行,所以其结果不会立即可用。它将保持挂起状态。如果处理器试图执行依赖于挂起结果的程序的其他部分,我们将会得到错误。
因此,处理器应该只执行不依赖于挂起结果的程序部分。为此,现代javascript使用了promises。
javascript中的promise是什么?
在javascript中,promise是异步函数返回的临时值。promise是javascript中现代异步编程的基础。
创建一个promise后,会发生以下两种情况之一。如果异步函数成功生成返回值,则会解决(resolve),如果发生错误,则会拒绝(reject)。这些都是promise生命周期中的事件。因此,我们可以附加事件处理程序到promise上,当它解决或拒绝时调用。
所有需要异步函数的最终值的代码都可以附加到promise的解决事件处理程序上。处理拒绝的promise的错误的所有代码也将附加到其对应的事件处理程序上。
下面是一个在nodejs中从文件中读取数据的示例。
const fs = require('fs/promises');
filereadpromise = fs.readfile('./hello.txt', 'utf-8');
filereadpromise.then((data) => console.log(data));
filereadpromise.catch((error) => console.log(error));
在第一行中,我们导入了fs/promises
模块。
在第二行中,我们调用了readfile函数,传入要读取内容的文件的名称和编码。这个函数是异步的,因此它返回一个promise。我们将promise存储在filereadpromise变量中。
在第三行中,我们附加了一个事件监听器,当promise解决时调用。我们通过在promise对象的then方法调用中传入要在promise解决时运行的函数作为参数来实现这一点。
在第四行中,我们附加了一个事件监听器,当promise拒绝时调用。这是通过调用catch方法,并将错误事件处理程序作为参数来完成的。
另一种方法是使用async和await关键字。我们将在下一节中介绍这种方法。
解释async和await关键字
可以使用async和await关键字以更好的语法方式编写异步javascript。在本节中,我将解释如何使用这些关键字以及它们对代码的影响。
await关键字用于在等待异步函数完成时暂停函数的执行。以下是一个示例:
const fs = require('fs/promises');
function readdata() {
const data = await fs.readfile('./hello.txt', 'utf-8');
// 在数据可用之前,此行将不会被执行
console.log(data);
}
readdata()
我们在调用readfile时使用了await关键字。这指示处理器在下一行(console.log)执行之前等待文件被读取。这有助于确保依赖于异步函数结果的代码直到结果可用才会被执行。
如果尝试运行上述代码,将会遇到错误。这是因为await只能在异步函数内部使用。要将函数声明为异步函数,需要在函数声明之前使用async
关键字,如下所示:
const fs = require(‘fs/promises');
async function readdata() {
const data = await fs.readfile(‘./hello.txt', ‘utf-8');
// 这一行在数据可用之前不会执行
console.log(data);
}
// 调用函数以使其运行
readdata()
// 在等待readdata函数完成的同时,此时的代码将运行
console.log(‘等待数据完成')
运行此代码片段,您将看到javascript在等待从文本文件读取的数据变得可用时执行外部console.log。一旦可用,将执行readdata中的console.log。
使用async和await关键字进行错误处理通常使用try/catch块。还重要了解如何使用异步代码循环。
异步和等待在现代javascript中可用。传统上,通过回调函数来编写异步代码。
介绍回调函数
回调是一种在结果可用时将被调用的函数。所有需要返回值的代码将放在回调内部。其他所有在回调之外的代码都不依赖于结果,因此可以自由执行。
以下是一个在nodejs中读取文件的示例。
const fs = require(“fs”);
fs.readfile(“./hello.txt”, “utf-8”, (err, data) => {
// 在此回调函数中,我们放置所有需要的代码
if (err) console.log(err);
else console.log(data);
});
在第一行中,我们导入了fs模块。接下来,我们调用了fs模块的readfile函数。readfile函数将从我们指定的文件中读取文本。第一个参数是文件,第二个参数指定文件格式。
readfile函数以异步方式从文件中读取文本。为此,它接受一个函数作为参数。此函数参数是一个回调函数,一旦数据被读取,就会被调用。
当回调函数被调用时传递的第一个参数是一个错误,如果函数运行时出现错误,它将有一个值。如果没有遇到错误,它将是undefined。
传递给回调的第二个参数是从文件中读取的数据。此函数内部的代码将访问文件中的数据。此函数之外的代码不需要文件中的数据,因此可以在等待文件数据时执行。
运行上面的代码将产生以下结果:
关键javascript功能
有一些关键功能和特性会影响异步javascript的工作方式。它们在下面的视频中得到了很好的解释:
我在下面简要概述了两个重要功能。
与允许程序员使用多个线程的其他语言不同,javascript只允许您使用一个线程。线程是逻辑上相互依赖的一系列指令。多个线程允许程序在遇到阻塞操作时执行一个不同的线程。
然而,多个线程增加了复杂性,使使用它们的程序更难理解。这使得引入代码错误的可能性更大,而且很难调试代码。javascript之所以是单线程的,是为了简单起见。作为单线程语言,它依赖于事件驱动来高效处理阻塞操作。
#2. 事件驱动
javascript也是事件驱动的。这意味着在javascript程序的生命周期中会发生一些事件。作为程序员,您可以将函数附加到这些事件上,每当事件发生时,附加的函数将被调用和执行。
某些事件可能是由于阻塞操作的结果可用而发生的。在这种情况下,相关的函数将被调用并传递结果。
编写异步javascript时需要考虑的事项
在这最后一节中,我将提到一些在编写异步javascript时需要考虑的事项。这将包括浏览器支持、最佳实践和重要性。
浏览器支持
这是一个显示不同浏览器中对promise支持的表格。
这是一个显示不同浏览器中对async关键字支持的表格。
最佳实践
- 始终选择async/await,因为它可以帮助您编写更干净、易于思考的代码。
- 在try/catch块中处理错误。
- 只在需要等待函数结果时使用async关键字。
异步代码的重要性
异步代码使您能够编写更高效的程序,只使用一个线程。这一点非常重要,因为javascript用于构建执行许多异步操作的网站,例如网络请求和读写磁盘上的文件。这种效率使得像nodejs这样的运行时在应用服务器中成为首选的运行时。
最后的话
这是一篇冗长的文章,但在其中,我们能够介绍异步函数与常规同步函数的区别。我们还介绍了如何使用承诺、async/await关键字和回调来使用异步代码。
此外,我们还介绍了javascript的关键特性。在最后一节中,我们总结了浏览器支持和最佳实践。
接下来,请查看node.js的常见面试问题。