与数据库交互看起来是一个很简单的事情,但由于Node.js的异步性质使得这一切并不是那么简单。通过Node.js编写异步代码有很多选择,每个都需要进行不同编码。在本系列中,我们将提供一些示例来说明如何利用各种异步模式获取、使用和关闭连接。在这篇文章中,我们将探讨异步编程与传统编程的区别。
try...catch...finally
JavaScript有一个try…catch…finally语句,try…catch部分很容易:运行某些代码并捕获异常,以便它们得到适当处理。但为什么我们需要finally?finally提供了一种允许你运行代码清理的方法,无论异常在try还是catch块中。清理代码通常包括关闭文件处理和数据库链接。
下面是例子:
如果你在浏览器控制台(或者Node.js)中运行,你会在控制到看到类似内容:
请注意catch块中引发的bar异常最终会作为未处理的异常,但在finally块执行之前不会起作用。
如果我们在Node.js中运行的所有代码都是异步,我们可以这样做:
不幸的是,try…catch…finally对于异步代码并不是非常有用—至少在异步函数可用之前,原因与Node.js中异步工作原理有关。
异步/事件处理
下面让我们看看异步操作如何执行,例如执行HTTP请求和执行数据库查询。
所有JavaScript代码在主线程运行,异步API从主线程调用并传递回调函数。根据类型不同,异步工作可能完全作为事件或者可能利用线程池。当异步工作完成时,回调函数被添加到回调对象,以便尽快在主线程上调用。
对于这种架构,在异步处理期间发生的错误会在完全不同的调用堆栈中带回到主线程。你不能捕捉在不同调用堆栈中引发的错误—为时太晚!
下面是调用异步API的非常简单的演示:
setTimeout(function() {
console.log('hello');
}, 2000);
console.log('world');
如果你在浏览器控制台或Node.js中运行该脚本,则输出是:
world
hello
在hello前两秒看到world会让异步编程新手感到惊讶。这里的秘密是传递到setTimeout的回调函数,它会“回调”,或者当指定时间过去时在主线程执行。
回调函数可追溯到JavaScript的开始,JavaScript被设计为将生活带到网络的语言。JavaScript开发人员需要将代码与事件关联(例如加载页面、点击鼠标或者按键),随着时间推移,下面这种DOM API被引入:
window.addEventListener('load', function() {
// do some work
});
上面的addEventListener方法将传入的异步函数作为第二个参数,并将其添加到windows元素load event相关的侦听器列表中。当该事件发生时,所有监听器被添加到回调对象,并将尽快被调用。
为了让回调正常工作,JavaScript中的函数需要重要的功能:它们需要是第一类对象,并需要关闭。
函数作为第一类
函数作为第一类对象的概念听起来很复杂。大多数编程语言提供了声明变量和创建命名代码单元的方法,通常被称为函数或过程。这两种结构之间通常有明确的区别,例如,你可以声明变量并将其传递给函数,但你不能将函数传递给函数。
另一方面,JavaScript允许函数可用作更标准数据类型(例如Number、String和Boolean)。函数可以被声明,从函数传递到函数,并可在未来某个时候被调用。函数作为第一类对象是传递函数到另一函数的前提条件。
想象一下,当页面加载以及当用户点击窗口时你想要执行相同的代码,你可以这样做:
window.addEventListener('load', function() {
// do some work
});
window.addEventListener('click', function() {
// do the same work here
});
上面代码的问题在于我们需要维持做相同工作的两个函数。然后,如果知道函数是第一类对象,我们可声明单一的命名函数,并根据需要传递引用。
function doWork() {
// do some work
}
window.addEventListener('load', doWork);
window.addEventListener('click', doWork);
你可以看到,函数作为第一类对象是简单而强大的JavaScript功能。
函数提供闭合
闭合的概念可能有点难理解,但它对异步/事件编程来说至关重要。简单来说,闭合是指在其封闭范围定义的变量的函数。
很多语言允许开发人员在函数中嵌套函数,子函数可引用父函数范围中声明的变量。使用其他语言的开发人员可能永远不会想到,如果在父函数完成执行后运行时调用子函数会发生什么?这根本不可能,但JavaScript并非如此!
请记住,JavaScript中的函数是第一类对象,所以与分配给变量的任何其他值一样,它们可通过传递来逃避其父函数的限制。当发生这种情况时,原始封闭范围(词汇范围)内对变量的引用仍将存在。那么,在以后调用子函数时应该怎么办?
闭合确保子函数能够访问这些变量,只要运行时可能需要调用子函数。这些变量不会像平常那样作为垃圾回收。
下面是示例:
你可将此代码复制并粘贴到具有.html扩展名的文件中,并在浏览器中打开。你会看到一个按钮写着“点击我”,当窗口加载时,onLoad函数会将onClick函数注册到按钮的click事件。
请注意,onClick不会在onLoad内调用。相反,应用被传递到API,可在未来调用该函数。因为onClick是指onLoad函数中声明的button变量,闭合可确保onClick在未来调用时可访问button。
现在我们已经探讨了JavaScript中异步编程相关的一些核心概念,下面让我们将注意力转移到Node.js中涉及的异步模式。
常见异步模式
目前,通过Node.js编写异步代码最常见(通用)模式是回调、异步模块和promise。Node.js 7.6版本升级到8版本,其中引入了被称为异步函数的异步处理新方法。
异步函数允许Javascript代码异步编写,且可异步执行。最重要的是,异步结构可按照你期望的方式运作。对于JavaScript来说,异步函数是重要变革,但promise以及异步处理仍然很重要。