手记

Node.js Web开发_第二章(2)

从nodejs.org下载安装Node.js安装包

Node.js官网https://nodejs.org/en/网站提供了Windows、macOS、Linux和Solaris等系统的二进制安装文件。我们只需进入Node.js官网,点击安装按钮,然后运行安装程序就可安装Node.js。对于带有包管理 的系统,如我们上面讨论的,最好使用包管理系统安装。这是因为你会发现使用包管理系统安装方式更加容易更新Node.js的最新版本。但是种安装方式并不适合所有开发者,原因如下:

  • 有些人更喜欢安装二进制文件,而不是使用包管理器安装软件。
  • 他们选择的系统没有包管理系统。
  • 他们的包管理系统中的Node.js实现已过时。

打开Node.js网站,您就会看到如下屏幕截图所示的内容。该页面尽最大努力确定您的操作系统并提 供适当的版本下载。如果您需要其他版本,请单击标题中的下载链接以获取所有可下载的版本:

macOS系统的Node.js安装包是一个PKG文件。不论是MacOS系统还是Windows系统,安装过程都和安装常规软件过程是一样的。
在安装Node.js程序过程中,命令行工具例如node和npm也会同时被安装。因此安装完成后,你可以直接使用npm和npx命令运行node.js程序。Windows系统还提供了一个预配置的Windows命令shell版本,可以很好地与Node.js配合使用。
正如您你刚刚了解到的,我们大多数人都喜欢使用Node.js安装包安装Node.js。但是,有时我们必须使用源代码安装Node.js。

在类似POSIX的系统上使用源代码安装

使用Node.js安装包安装Node.js发行版是首选的安装方法。但是使用源代码安装Node.js具有以下几个优点:

  • 你可以根据需要优化编译器设置。
  • 你可以交叉编译,比如编译嵌入式ARM系统。
  • 你可以根据需要安装多个Node.js版本以进行测试。
  • 你可能正在处理Node.js。

现在您已经了解为什么要使用源代码安装Node.js,让我们通过一些构建脚本来动手安 装Node.js。安装过程遵配置、制作和安装程序等常规安装过程,您可能已经使用其他开源软件包执行了该过程。如果没有,请不要担心,我将指导你完成整个过程。
官方安装说明文件见README.md文件,网址为https://github.com/nodejs/node/blob/master/README.md。

安装前提条件

在使用源代码安装Node.js前,我们需要安装三个工具:C编译器、Python和OpenSSL库。Node.js编译过程检查会检测这个三款个工具是否 存在。如果C编译器或Python不存在,则编译将失败。请用以下命令将检查C编译器和Python是否存已安装:

$ cc --version
Apple LLVM version 10.0.0 (clang-1000.11.45.5)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xct oolchain/usr/bin 
$ python
Python 2.7.16 (default, Oct 16 2019, 00:35:27)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information. >>>

具体的安装方法取决于您的操作系统。
Node.js构建工具正在更新以支持Python 3.x。Python2.x正处于生命周期结束阶段,计划于2019年底结束维护,因此建议你将Python2.x更新到Python3.x。
在编译Node.js源代码之前,我们必须安装正确的工具。在macOS上,有几个特殊的事项需要注意。

在macOS上安装开发者工具

开发工具(如GCC)是macOS上的可选安装工具。幸运的是,这些很容易获得。
你可以首先安装Xcode,Xcode可以通过Mac应用商店免费下载。只需搜索Xcode并单击“获取”按钮。安 装Xcode后,打开终端窗口并输入以下内容:

$ xcode-select --install

这就安装了Xcode命令行工具

现在我们已经安装了需要的工具,可以继续编译Node.js源代码。

在所有类POSIX的系统使用源代码安装Node.js

使用源代码编译安装Node.js的过程如下:

  1. 源代码下载地址http://nodejs.org/download。
  2. 使用./Configure配置用于构建的源代码。
  3. 运行make,然后运行make install

源代码绑定包可以通过浏览器下载,也可以按以下方式下载替换你喜欢的版本:

$ mkdir src
$ cd src
$ wget https://nodejs.org/download/release/v14.0.0/node-v14.0.0.tar.gz 
$ tar xvfz node-v14.0.0.tar.gz
$ cd node-v14.0.0

现在我们通过配置源代码来构建Node.js。与许多其他开源包一样,有一系列选项可用于自定义构建Node.js:

$ ./configure --help

要将Node.js安装到你的主目录,请运行以下命令:

$ ./configure --prefix=$HOME/node/14.0.0 ..output from configure

如果要同时安装多个版本的Node.js,请使用以下命令将将版本号配置到PATH中。这样,每个版本将被安装 到一个单独的目录中。然后,通过更改响应的PATH变量就可以很简单地在Node.js的版本之间切换:

#On bash shell:
$ export PATH=${HOME}/node/VERSION-NUMBER/bin:${PATH} # On csh
$ setenv PATH ${HOME}/node/VERSION-NUMBER/bin:${PATH}

安装多个Node.js版本的一种更简单的方法是使用nvm脚本,这种安装方式我们将在后面了解。
如果要将Node.js安装到系统目录中,只需省略–prefix,Node.js就会被默认安装到系统目录/usr/ local中。
等待一段时间,安装就会停止,并且可能已经成功地配置了源代码树,以便将Node.js安装到你选择的目录中。如果安装不成功,则打印的错误消息将描述需要修复的内容。一旦满足配置脚本的要求,就可以进入下一 步。
在满足配置脚本的情况下,您可以编译软件:

$ make
.. a long log of compiler output is printed 
$ make install

如果要将Node.js安装到系统目录中,请使用以下代码执行最后一步:

$ echo 'export PATH=$HOME/node/14.0.0/bin:${PATH}' >>~/.bashrc 
$ . ~/.bashrc

或者,对于csh用户,使用以下语法导出生成的环境变量:

$ echo 'setenv PATH $HOME/node/14.0.0/bin:${PATH}' >>~/.cshrc 
$ source ~/.cshrc

安装构建时,上面的命令会创建一个目录结构,如下所示:

$ ls ~/node/14.0.0/
bin   include   lib   share $ ls ~/node/14.0.0/bin
node npm npx

现在,我们已经了解了如何在类UNIX系统上使用源代码安装Node.js,我们可以在Windows上执行同样的 操作。

在Window上使用源代码安装Node.js

前面引用的BUILDING.md文档包含了安装说明。您可以使用Visual Studio的构建工具或完整的Visual Studio 2017或2019产品安装Node.js:

需要另外三个工具:

然后,运行内置的.\vcbuild脚本执行构建。
我们已经学习了如何安装一个Node.js实例,现在让我们来了解一下如何安装多个实例。

使用nvm安装多个版本Node.js

我们通常不会安装多个版本的Node.js,因为这样做会增加系统的复杂性。但是,如果您正在对Node.js进行模拟黑客攻击演练,或者针对不同的Node.js版本测试你的应用,那么你可能需要安装多个Node.js实例。安装方法只是对前面安装方法进行简单的修改。
在前面讨论从源代码构建Node.js时,我们注意到可以在单独的目录中安装多个Node.js实例。如果您需要 定制的Node.js构建,那么只需要使用源代码安装就可以了。但大多数人都比较喜欢使用Node.js安装包安 装Node.js并且也可以安装在单独的目录中。
在Node.js版本之间切换只需要将安装Node.js的目录添加到PATH变量中,POSIX系统使用如下代码:

$ export PATH=/usr/local/node/VERSION-NUMBER/bin:${PATH}

但是时间一长,维护会变得越来越困难。因为我们需要对不同版本的Node.js进行设置,包括npm和我们 要是用的第三发模块。此外,频繁更改PATH变量也是非常麻烦的,因此有开发者创建了几个版本管理 器,以简化对多个Node.js/npm版本的管理,并提供了更改PATH变量的智能命令:

这两个版本管理器都用于维护Node.js的多个版本,并且能够让你非常简单在不同的Node.js版本之间切 换。安装说明可在这个工具的官方网站上获得。例如,使用nvm,你可以运行以下命令:

$ nvm ls
...
v6.4.0
...
v6.11.2
v8.9.3
v10.15.2
...
v12.13.1
...
v14.0.0
-> system
default -> 12.9.1 (-> v12.9.1)
node -> stable (-> v12.13.1) (default) stable -> 12.13 (-> v12.13.1) (default) 
$ nvm use 10
Now using node v10.15.2 (npm v6.4.1)
$ node --version
v10.15.2
$ nvm use 4.9
Now using node v4.9.1 (npm v2.15.11)
$ node --version
v4.9.1
$ nvm install 14
Downloading and installing node v14.0.0... Downloading 
https://nodejs.org/dist/v14.0.0/node-v14.0.0-darwin-x64.tar.xz... ############... 100.0%
Computing checksum with shasum -a 256
Checksums matched!
Now using node v14.0.0 (npm v6.14.4)
$ node --version
v14.0.0
$ which node
/Users/david/.nvm/versions/node/v14.0.0/bin/node
$ /usr/local/bin/node --version
v13.13.0
$ /opt/local/bin/node --version
v13.13.0

在本例中,我们首先列出了已安装的版本。然后,我们演示了如何切换,并检验切换后的版本。我们还安装了nvm的新版本。最后,我们展示了nvm安装Node.js软件包的目录,以及使用MacPorts或Homebrew安装的Node.js版本。
这表明您可以在系统目录中安装Node.js,由nvm管理多个私有的Node.js版本,并根据需要在它们之间切换。新的Node.js版本发布后,即使您的操作系统的官方软件包管理器尚未更新其软件包,也可以使用nvm轻松安装。

在Windows上安装nvm

不幸的是,nvm不支持Windows。幸运的是,有几个Windows系统的nvm克隆版本:

另一种方法是使用WSL。因为在WSL中,Windows 10自带了Linux子系统,所以可以使用nvm本身。但是,让我们继续关注如何在Windows中安装nvm。
本书中的许多示例都使用nvm-windows应用程序进行了测试。虽然有轻微的执行差异,但其执行与Linux和macOS上的nvm基本相同。最大的变化是nvm use和nvm install命令中的版本号说明符。
使用nvm for Linux和macOS,您可以输入一个简单的版本号,例如nvm use 8,它将自动替换命 名Node.js的最新版本。对于nvm-Windows,nuv use 8与nvm use 8.0.0的作用是相同的。换句话说,对 于nvm-windows,您必须使用确切的版本号。幸运的是,使用nvm list available命令可以轻松获得受支持版本的列表。
使用诸如nvm之类的工具简化了针对多个Node.js版本测试Node.js应用程序的过程。
现在我们可以安装Node.js了,我们需要确保我们正在安装任何我们想要使用的Node.js模块。这需要在我们的计算机上安装构建工具。

安装原生代码模块的要求

虽然在本书中我们不会讨论原生代码模块开发,但我们确实需要确保它们可以构建。NPM存储库中的一 些模块是原生代码,它们必须用C或C++编译器编译,以构建相应的.No.Frm文件(.No.Exchange用于二 进制原生代码模块)。
该模块通常是其他库的包装器。例如,libxslt和libxmljs模块是同名C/C++库的包装器。该模块包含C/C++源代码,安装后会自动运行使用node-gyp编译的脚本。
node-gyp工具是一个用node.js编写的跨平台命令行工具,用于编译node.js的原生附加模块。我们多次 提到的原生代码模块就是用这个工具编译的,用于与Node.js一起使用。
通过运行以下命令,您可以很容易地看到这一点:

$ mkdir temp
$ cd temp
$ npm install libxmljs libxslt

这是在临时目录中完成的,因此您可以在以后删除它。如果您的系统没有安装编译原生代码模块的工 具,您将看到错误消息。否则,您将在输出中看到一个node-gyp执行,后面是许多显然与编译C/C++文 件相关的文本行。
node-gyp工具的先决条件与从源代码编译node.js的先决条件类似,即C/C++编译器、Python环境和其他构建工具,如Git。对于Unix、macOS和Linux系统,这些都很容易获得。对于Windows,您应该安装以下组件:

通常,您不需要安装node-gyp,这是因为它作为npm的一部分自动安装的。这样,npm就可以自动构建原生代码模块。
其GitHub存储库包含文档,请访问:https://github.com/nodejs/node-gyp。
阅读其存储库中的node-gyp文档将使您更清楚地了解前面讨论的编译先决条件以及开发原生代码模块。 这是一个非显式依赖关系的示例。最好明确声明软件包所依赖的所有内容。在Node.js中,依赖项在package.json中声明,以便包管理器(npm或Thread)可以下载和设置所有内容。但这些编译器工具 是由操作系统包管理系统设置的,不受npm或yarn的控制。因此,我们不能显式地声明这些依赖项。
我们刚刚了解到Node.js不仅支持JavaScript编写的模块,还支持其他编程语言编写的模块。我们还学习了如何安装此类模块。接下来,我们将了解Node.js的版本号。

选择要使用的Node.js版本和版本策略

在上一节中,我们只是看到了太多不同的Node.js版本号,您可能会对使用哪个版本感到困惑。本书使用的Node.js版本为14.x,我们将介绍的所有内容都与Node.js 10.x和任何后续版本兼容。
从Node.js 4.x开始,Node.js团队采用了双轨方法。偶数版本(4.x、6.x、8.x等等)是他们所谓的长期支 持(LTS),而奇数版本(5.x、7.x、9.x等等)是当前新特性开发版本。虽然开发分支保持稳定, 但LTS版本定位为供生产使用,并将在几年内得到更新。
在撰写本文时,Node.js 12.x是当前的LTS版本;Node.js 14.x已经发布,最终将更新为LTS版本。
除了通常的性能改进和bug修复之外,各个新的Node.js版本的一个主要影响是引入了最新的V8 JavaScript引擎版本。反过来,这意味着在V8团队在维护V8引擎时会不断引入更多的ES2015/2016/2017功 能。Node.js 8.x新增了async/await函数,Node.js 10.x支持标准的ES6模块格式,Node.js 14.x将完全支 持ES6模块格式。
我们要考虑新的Node.js版本是否会破坏您的代码。随着V8不断被加入ECMAScript新特性,并 且Node.js团队有时会对Node.js API进行破坏性的更改,新的语言特性总是被添加。如果您的代码已经在 某个Node.js版本上进行了测试,那么它是否可以在早期版本上运行?Node.js的更改会打破我们的一些假 设吗?
npm的作用确保我们的包在正确的Node.js版本上执行。这意味着我们可以在package.json文件中为包指定兼容的Node.js版本(我们将在第3章探索Node.js模块中进行探讨)。
我们可以向package.json添加一个版本条件,如下所示:

engines: { 
     "node": ">=8.x"
 }

该代码要求给定的包与Node.js 8.x或更高版本兼容。
当然,您的开发环境可以安装多个Node.js版本。您需要声明软件支持的版本,以及您希望评估的任何更 高版本。
我们刚刚了解了Node.js社区如何管理发行版和版本号。我们的下一步是讨论要使用的编辑器。

为Node.js选择编辑器和调试器

由于Node.js代码是JavaScript,因此所有支持JavaScript的编辑器都可以选择。不像其他一些语言那么复 杂,以至于带代码补全功能是IDE必须具有的,一个简单的编程编辑器完全可以满足Node.js开发的需 求。
有两个编辑器值得一提,因为它们本身就是用Node.js编写的:Atom和Microsoft Visual Studio Code。 Atom(https://atom.io/)) 将自己描述为21世纪的可高度自定义的编辑器。它可以通过使用Atom API编 写Node.js模块进行扩展,并且配置文件很容易编辑。换句话说,它可以通过与许多其他编辑器一样的方 式进行目定斗,也就是说,您可以编写一个软件模块来增加编辑器的功能。Electron框架是为了构 建Atom而发明的,它是使用Node.js构建桌面应用程序的一种非常简单的方法。
Microsoft Visual Studio Code(https://code.visualstudio.com/) 也是一个可高度自定义编辑器,并且也 是开源的,并使用Electron开发的。然而,它不是一个空洞的“我也是”编辑器,在走Atom的路同时 不,Visual Studio代码本身就是一个可靠的程序员编辑器,并且也有很多有趣的功能。
对于调试器,有几个有趣的选择。从Node.js 6.3开始,inspector协议使使用Google Chrome调试器成为 可能。Visual Studio Code有一个内置调试器,也使用inspector协议。
有关调试选项和工具的完整列表,请参阅 https://nodejs. org/en/docs/guides/debugging-getting-started/。
与编辑器相关的另一项任务是添加扩展以提高编程体验。大多数面向程序员的编辑器允许您扩展功能并 协助编写代码。一个简单的例子是JavaScript、CSS、HTML等的语法着色功能。代码补全扩展是编辑器帮 助您编写代码功能。一些扩展扫描用于常见错误的语法错误代码;这些扩展通常使用lint这个词表示。一 些扩展有助于运行单元测试框架。因为的选择的编辑器实在太多,我们不能提供具体的建议。
对于一些人来说,编程编辑器的选择是一个严肃的问题,因此我们谨慎地建议您使用您喜欢的编辑器, 只要它有助于您编写JavaScript代码即可。接下来,我们将学习Node.js命令和一些关于运行Node.js脚本 的知识。

运行和测试命令

现在您已经安装了Node.js,我们想做两件事来验证安装是否成功,并熟悉Node.js命令行工具和使 用Node.js运行简单脚本。我们还将再次讨论异步函数,并看一个简单的HTTP服务器示例。我们将使 用npm和npx命令行工具耒结束本章。

使用Node.js的命令行工具

Node.js有两个基础安装命令:node和npm。我们已经看到正在运行node,主要用于运行命令行脚本或 服务器进程。另一个是npm,它是Node.js的包管理器。
验证Node.js是否安装的最简单的有效方法是获取Node.js帮助。输入以下命令:

$ node --help
Usage: node [options] [ -e script | script.js | - ] [arguments]
node inspect script.js [arguments]
Options:
-v, --version print Node.js version
-  e, --eval script evaluate script
-  p, --print evaluate script and print result
-  c, --check syntax check script without executing
-  i, --interactive always enter the REPL even if stdin
does not appear to be a terminal
-  r, --require module to preload (option can be repeated)
-  script read from stdin (default; interactive mode if a tty)
... many more options
Environment variables:
NODE_DEBUG ','-separated list of core modules that should print debug information 
NODE_DEBUG_NATIVE ','-separated list of C++ core debug categories that should print debug output
NODE_DISABLE_COLORS set to 1 to disable colors in the REPL
NODE_EXTRA_CA_CERTS path to additional CA certificates file
NODE_NO_WARNINGS set to 1 to silence process warnings
NODE_OPTIONS set CLI options in the environment via a space-separated list
NODE_PATH ':'-separated list of directories prefixed to the module search path
... many more environment variables

我们得到了很多的输出内容,这内容不需详细了解。关键在于node–help提供了很多有用的信息。
请注意,Node.js和V8都有选项(在前面的命令行中未显示)。记住Node.js是构建在V8之上的;它有自己的选项体系,主要是关于字节码编译或垃圾收集和堆算法的细节。输入node --v8-options就可以查看 这些选项的完整列表。
我们可以在命令行上指定选项、单个脚本文件以及该脚本的参数列表。我们将在下一节中进一步讨论脚 本参数并使用Node.js运行一个简单的脚本。
运行没有参数的Node.js可以让我们使用JavaScript shell直接编写运行JavaScript代码:

$ node
> console.log('Hello, world!'); Hello, world! undefined

我们在Node.js脚本中编写的所有代码都可以在这里编写。命令解释器提供了良好的面向终端的用户体 验,对于以交互方式处理代码非常有用。

使用Node.js运行简单脚本

现在,让我们看看如何使用Node.js运行脚本。这很简单;让我们从前面显示的帮助信息开始。命令行模式只是一个脚本文件名和一些脚本参数,任何使用其他语言编写脚本的人都应该熟悉这些参数。
我你可以使用任意处理的文本编辑器来创建和编辑Node.js脚步,
如VI/VIM、Emacs、Notepad++、Atom、Visual StudioCode、Jedit、BB Edit、TextMate或Komodo等等。如果是一个面向程序员的编辑器,即使只是语法着色,也是很有帮助的。
对于本书中的源代码文件,放在哪里并不重要。但是,为了整洁起见,您可以在计算机的主目录中创建 一个名为node-web-dev的目录,并在该目录中给每个章节创建一个目录(例如,chap02和chap03)。 首先,创建一个名为ls.js的文件,其中包含以下内容:

const fs = require('fs').promises;
async function listFiles() {
   try {
     const files = await fs.readdir('.'); 
     for (const file of files) { 
       console.log(file);
      }
     } catch (err) { 
        console.error(err);
     }
   }
   listFiles();

接下来,在命令框中输入以下命令来运行文件。(译者注:如果js文件不在当前命令框所在目录的话,你需要先 将命令框定位到相应目录下)

$ node ls.js ls.js

这是对unix ls命令的一个非常简单的模仿(好像你不能从名称中看出!)。readdir函数与用于列出目录 中文件的Unix readdir系统调用命令非常相似。在Unix/Linux系统上,我们可以运行以下命令以了解更多 信息:

$ man 3 readdir

当然,man命令可以让你阅读手册页面和第3部介绍的C库。
在函数体内,我们读取目录并打印其内容。使用require.(‘fs’).promises为我们提供了返回承诺的fs模块 (文件系统函数)的版本;因此,fs模块在异步函数中运行良好。同样,ES2015 for…of循环构造允许我们 以一种在异步函数中运行良好的方式循环数组中的元素。
默认情况下,fs模块函数使用最初为Node.js创建的回调范例。因此,大多数Node.js模块都使用回调范例。在异 步函数中,如果函数返回承诺,则更方便,这样就可以使用await关键字。util模块提供了一个函 数util.promisify,它为旧式的面向回调的函数生成一个包装函数,因此它返回一个承诺。
这个脚本被硬编码列出当前目录中的文件。真正的ls命令需要一个目录名,因此让我们稍微修改一下该脚 本。
命令行参数位于名为process.argv的全局数组中。因此,我们可以修改ls.js,将其复制为ls2.js(如下所 示),以查看此数组的工作方式:

const fs = require('fs').promises;
async function listFiles() {
   try {
     var dir = '.';
     if (process.argv[2]) dir = process.argv[2]; 
     const files = await fs.readdir(dir); 
     for (let fn of files) { 
        console.log(fn);
       }
        } catch (err) { 
         console.error(err);
    }
  }
  listFiles();

你可以按如下方式运行:

$ pwd 
/Users/David/chap02
$ node ls2 ..
chap01
chap02
$ node ls2
app.js
ls.js
ls2.js

我们只是检查命令行参数是否存在,if(process.argv[2])。如果存在,则重写dir变 量,dir=process.argv[2]的值,然后将其用作readdir参数:

$ node ls2.js /nonexistent
{ Error: ENOENT: no such file or directory, scandir '/nonexistent' 
errno: -2,
code: 'ENOENT',
syscall: 'scandir',
path: '/nonexistent' }

如果给定的目录路径不存在,将抛出一个错误,并使用catch语句打印。

编写内联异步箭头函数

有一种不同的方式来写这些例子,有些人觉得更简洁。这些示例是用常规函数编写的,带有function关键 字声明,但前面有async关键字。ES2015新增了一个特性,即箭头函数,让我们稍微简化了代码。
结合async关键字,async 箭头函数如下所示:

async () => {
  // function body
}

你可以在任何地方使用箭头函数。例如,该函数可以指定给变量,也可以作为回调传递给另一个函数。 当与async关键字一起使用时,箭头函数体具有异步函数的所有行为。
对于这些示例,可以使用异步箭头函数以立即执行:

(async () => {
  // function body })()

最后一个括号将使内联函数立即被调用。
然后,因为异步函数返回一个承诺,所以有必要添加一个.catch块来捕获错误。综上所述,更改后的示例
如下所示:

const fs = require('fs');
(async () => {
  var dir = '.';
  if (process.argv[2]) dir = process.argv[2]; 
  const files = await fs.readdir(dir);
  for (let fn of files) {
    console.log(fn);
  }
})().catch(err => { console.error(err); });

更改前后的代码哪一种更可取,取决于个人喜好。但是,您会发现这两种函数编写方式都在使用,因此 有必要了解掌握。
在脚本的顶部调用异步函数时,有必要捕获并报告所有错误。未能捕获和报告错误可能导致难以确定的 神秘问题。对于本例的原始版本,错误是通过try/catch块显式捕获的。在这个版本中,我们使用.catch块 捕获错误。
在异步函数之前,我们使用了Promise对象,在此之前,我们使用回调范式。Node.js中仍然使用这三种 范式,这意味着您需要了解每一种范式。

转换为异步函数和Promise

在上一节中,我们讨论了util.promisify及其如向将面向回调的函数转换为返回承诺的函数。后者可以很好 地处理异步函数,因此,函数最好返回一个承诺。
更准确地说,util.promisify将被赋予一个使用错误优先回调范式的函数。此类函数的最后一个参数是回调 函数,它的第一个参数被解释为错误指示符,因此称为error-first-callback(错误优先回 调)。util.promisify返回的是另一个返回承诺的函数。
承诺的作用与错误优先回调相同。如果显示错误,承诺将解析为拒绝状态,而如果显示成功,承诺将解 析为成功状态。正如我们在这些示例中看到的,在异步函数中可以很好地处理承诺。
Node.js生态系统有大量使用错误优先回调的函数。社区已经开始了一个转换过程,其中函数将返回一个 承诺,并且可能还将首先进行错误回调,以实现API兼容性。
Node.js 10中的一个新特性就是这种转换的一个例子。fs模块中有一个子模块,名为fs.promises,具有相 同的API,但生成Promise对象。我们使用该API编写了前面的示例。
另一个选择是第三方模块fs-extra。此模块具有标准fs模块之外的扩展API。一方面,如果没有提供回调函 数或调用回调函数,该模块的函数将返回一个承诺。此外,该模块还包合几个有用的函数。
在本书后面章节中,我们将经常使用fs-extra,因为我们要使用到模块的其他额时函数。有关该模块的文档,请 访https://www.npmjs.com/package/fs-extra。
util模块有另一个util.callbackify函数,该函数顾名思义将返回承诺的函数转换为使用回调函数的函数。
现在我们已经了解了如何运行一个简单的脚本,让我们看看一个简单的HTTP服务器。

使用Node.js启动服务器

您要运行的许多脚本都是服务器进程;我们后面会运行很多这样的脚本。为了让你能够熟练地安装验证 和使用Node.js,因此我们想要运行一个简单的HTTP服务器。让我们借用Node.js主页上的简单的服务器 脚本(http://nodejs.org)。
创建一个名为app.js的文件,其中包含以下内容:

const http = require('http');
http.createServer(function (req, res) { res.writeHead(200, 
  {'Content-Type': 'text/plain'}); res.end('Hello, World!\n');
}).listen(8124, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8124');

按如下方式运行:

$ node app.js
Server running at http://127.0.0.1:8124

这是可以使用Node.js构建的最简单的web服务器。如果您对它的工作原理感兴趣,请直接阅读 第4章“HTTP服务器和客户端”、第5章“您的第一个Express应用程序”以及第6章“实现移动优先范式”。但是 现在,只是在中输入http://127.0.0.1:8124 查看Hello,World!信息:

一个需要思考的问题是,当ls.js退出时,为什么这个脚本没有退出。在这两种情况下,脚本的执行都会到达文件的末尾;Node.js进程不会在app.js中退出,而会在ls.js中退出。
原因是存在活动事件侦听器。Node.js总是启动一个事件循环,在app.js中,listen函数创建一个实 现HTTP协议的事件(listener)。此侦听器事件将保持app.js运行,直到您执行某些操作,例如在终端窗 口中按Ctrl+C。在ls.js中,不会创建长时间运行的侦听器事件,因此当ls.js到达其脚本末尾时,Node进程将退出。
要使用Node.js执行更复杂的任务,我们必须使用第三方模块。这时候就该npm存储库出场了。

使用npm,Node.js包管理器

Node.js是一个JavaScript解释器,有几个有趣的异步I/O库,它本身就是一个非常基础的系统。Node.js的 有趣之处之一是Node.js的第三方模块生态系统的快速增长。
该生态系统的核心是npm模块存储库。虽然Node.js模块可以作为源代码下载并手动组装以便用 于Node.js程序,但这样做很繁琐,而且很难实现可重复的构建过程。npm给了我们一个更简单的方法;npm是Node.js的标准包管理器,它大大简化了下载和使用这些模块的难度。我们将在下一章详细讨论npm。
你们中目光敏锐的人会注意到,我你已经使用前面讨论的所有安装方法安装了nmp。过去,npm是单独 安装的,但现在它与Node.js是捆绑在一起的。
现在我们已经安装了npm,让我们来快速了解一下。hexy程序是一个用于打印十六进制文件转储的实用 程序。这是一款七十年代的程序,但它仍然非常有用。它给我们提供了快速安装和试用的功能:

$ npm install -g hexy
/opt/local/bin/hexy -> 
/opt/local/lib/node_modules/hexy/bin/hexy_cmd.js+ hexy@0.2.10 added 1 package in 1.107s

添加-g标志使模块全局可用,而与命令shell的当前工作目录无关。当模块提供命令行界面时,全局安装最有用。当程序包提供命令行脚本时,npm会设置该脚本。我已正确地全局安装hex ,供计算机的所有 用户使用。
根据Node.js的安装方式,它可能需要与sudo一起运行:

$ sudo npm install -g hexy

安装后,您可以通过以下方式运行新安装的程序:

$ hexy --width 12 ls.js
00000000: 636f 6e73 7420 6673 203d 2072 const.fs.=.r 
0000000c: 6571 7569 7265 2827 6673 2729 equire('fs') 
00000018: 3b0a 636f 6e73 7420 7574 696c ;.const.util 
00000024: 203d 2072 6571 7569 7265 2827 .=.require(' 
00000030: 7574 696c 2729 3b0a 636f 6e73 util');.cons 
0000003c: 7420 6673 5f72 6561 6464 6972 t.fs_readdir 
00000048: 203d 2075 7469 6c2e 7072 6f6d .=.util.prom 
00000054: 6973 6966 7928 6673 2e72 6561 isify(fs.rea 
00000060: 6464 6972 293b 0a0a 2861 7379 ddir);..(asy 
0000006c: 6e63 2028 2920 3d3e 207b 0a20 nc.().=>.{.. 
00000078: 2063 6f6e 7374 2066 696c 6573 .const.files 
00000084: 203d 2061 7761 6974 2066 735f .=.await.fs_ 
00000090: 7265 6164 6469 7228 272e 2729 readdir('.') 
0000009c: 3b0a 2020 666f 7220 2866 6e20 ;...for.(fn. 
000000a8: 6f66 2066 696c 6573 2920 7b0a of.files).{. 
000000b4: 2020 2020 636f 6e73 6f6c 652e ....console. 
000000c0: 6c6f 6728 666e 293b 0a20 207d log(fn);...} 
000000cc: 0a7d 2928 292e 6361 7463 6828 .})().catch(
000000d8: 6572 7220 3d3e 207b 2063 6f6e err.=>.{.con 
000000e4: 736f 6c65 2e65 7272 6f72 2865 sole.error(e 
000000f0: 7272 293b 207d 293b rr);.});

hexy命令作为全局命令安装,使其易于运行。
在下一章中,我们将再次深入探讨npm。hexy实用程序既是一个Node.js库,也是一个用于打印这些老式 十六进制转储的脚本。
在开源世界中,满足不同开发者的需求是开源项目创建的不一刀衷。启动Thread项目的人员发rnnpm没有解决 的开发者的需求,因此创建了一个替代包管理器(Package Manager)的工具,即yarn。他们声 称yarn与npm相比有许多优势,主要是在性能方面。要了解有关yarn的更多信息,请转到 https://yarnpkg.com/。
对于本书中使用npm的每个示例,都有一个使用纱线的近似等效命令。
对于打包的npm命令行工具,有另一种更简单的方法来使用该工具。

使用npx执行Node.js打包的二进制文件

npm存储库中的一些包是命令行工具,比如我们前面看到的hexy程序。这些命令行工具在使用之前必须 先安装,这比较麻烦。在安装node.js时,细心的你一定注意到了npx与node和npm命令是一起安装 的。npx旨在通过消除首先要安装命令行软件包的需要,简化从npm存储库运行命令行工具的过程。
上一个示例可以这样运行:

$ npx hexy --width 12 ls.js

在这背后,npx使用npm将包下载到缓存目录,除非包已经安装到当前项目目录中。由于该包位于缓存目 录中,因此只需下载一次。
该工具有许多有趣的选项;要了解更多信息,请转到www.npmjs.com/package/npx。
在本节中,我们学习了很多Node.js自带的命令行工具,并运行了一个简单的脚本和HTTP服务器。接下 来,我们将了解JavaScript语言的进步如何影响Node.js平台。

使用ECMAScript 2015、2016、2017及以后的版本提升Node.js

2015年,ECMAScript委员会发布了期待已久的JavaScript语言的重大更新。更新为JavaScript带来了许多新特性,如承诺、箭头函数和类对象。语言更新为改进奠定了基础,因为它将极大地提高我们编写干 净、可理解的JavaScript代码的能力。
浏览器制造商正在添加这些急需的功能,这意味着V8引擎也在添加这些功能。从4.x版开始,这些特性就 加入进了Node.js。
要了解Node.js中ES2015/2016/2017/等的当前状态,请访问https://nodejs.org/en/docs/es6/。
默认情况下,Node.js仅启用V8认为稳定的ES2015、2016和2017功能。可以使用命令行选项启用其他功 能。几乎完整的特性是通过–es_staging选项启用的。网站文档提供了更多信息。
node green网站(http://node.green/) 有一个表,列出Node.js版本中一长串功能的状态。
ES2019语言规范发布在https://www.ecma-international.org/publications/standards/Ecma-262.htm。 TC-39委员会在GitHub上开展工作,网址是:https://github. com/tc39。
ES2015(及更高版本)功能对JavaScript语言进行了重大改进。承诺(Promise)类的一个特性是对Node.js编程中常见习惯用法的根本性反思。在ES2017中,一对新关键字async和await简化了 在Node.js中编写异步代码的过程,这将鼓励Node.js社区进一步反思平台的常见的习惯用法。
新的JavaScript增加了很特性,但让我们快速浏览一下我们将广泛使用的这两个特性。
第一种是较轻的函数语法,称为简头函数:

fs.readFile('file.txt', 'utf8', (err, data) => { 
  if (err) ...; // do something with the error 
  else ...;  // do something with the data
});

这不仅仅是用箭头代替function关键字的语法糖优势。箭头函数更简洁,也更容易阅读。简洁的代价是改 变箭头函数中this的值。在普通函数中,this在函数内具有一个唯一的值。在箭头函数中,this的值与箭头 函数的作用域相同。这意味着,当使用箭头函数时,我们不必将this引入回调函数中,因为this在两个函
数中是一样的。
下一个特性是Promise(承诺)类,用于延迟和异步计算。延迟代码执行以实现异步行为是Node.js的一 个关键范例,它需要两个惯用约定:

  • 异步函数的最后一个参数是回调函数,在执行异步执行时调用回调函数。
  • 回调函数的第一个参数是错误指示器。

这些约定虽然方便,但却导致了难以理解和维护的多层代码金字塔:

  doThis(arg1, arg2, (err, result1, result2) => { 
      if (err)...;
      else {
        // do some work
        doThat(arg2, arg3, (err2, results) => { 
          if (err2)...;
          else { 
            doSomethingElse(arg5, err => { 
              if (err)..;
              else..;
            });
          }
        });
      }
    });

你不需要理解代码;这只是我们使用回调时实际发生的情况的概要。根据特定任务需要的步骤,代码金 字塔可能会变得越来越深。承诺将让我们解决代码金字塔问,提高代码的可靠性,因为错误处理更直 接,更容易捕获所有错误。
Promise类的创建如下所示:

    function doThis(arg1, arg2) {
      return new Promise((resolve, reject) => { 
        // execute some asynchronous code
        if (errorIsDetected) return reject(errorObject); 
        // When the process is finished call this: 
        resolve(result1, result2);
      });
    }

调用方接收承诺对象,而不是传入回调函数。当正确使用时,前面的金字塔代码可以按如下方式重构:

doThis(arg1, arg2)
        .then((result) => {
          // This can receive only one value, hence to 
          // receive multiple values requires an object or array 
          return doThat(arg2, arg3);
        })
        .then((results) => {
          return doSomethingElse(arg5);
        })
        .then(() => {
          // do a final something
        })
        .catch((err) => {
          // errors land here
        });

这是因为如果then函数返回Promise对象,那么Promⅰse类支持链式。
async/await特性实现promise类的承诺,以简化异步编码。此功能在异步函数中变为活跃状态:

async function mumble() {
 // async magic happens here
}

异步箭头函数如下所示:

const mumble = async () => { 
 // async magic happens here
};

为了了解异步函数范例给我们带来了多大的改进,让我们重新编写前面的示例,如下所示:

async function doSomething(arg1, arg2, arg3, arg4, arg5) { 
 const { result1, result2 } = await doThis(arg1, arg2); 
 const results = await doThat(arg2, arg3);
 await doSomethingElse(arg5);
 // do a final something
 return finalResult;
}

同样,我们不需要理解代码,只需要看看它的结构。与我们开始使用的嵌套结构相比,是不是感觉舒服爽多了?
wait关键字与Promise一起使用。它会自动等待承诺的解析。如果承诺解析成功,则返回值,如果承诺解 析为错误,则抛出该错误。处理结果和抛出错误都以通常的方式处理。
此示例还显示了ES2015的另一个功能:解构。可以使用以下代码提取对象的字段:

const { value1, value2 } = {
 value1: "Value 1", value2: "Value 2", value3: "Value3"
};

这例子演示了一个对象有三个字段,但只提取其中两个字段。
为了继续探索JavaScript的进步,让我们来看看Babel。

0人推荐
随时随地看视频
慕课网APP