本文转载自:众成翻译
译者:网络埋伏纪事
连接:http://www.zcfy.cc/article/1759
原文:https://blog.risingstack.com/node-hero-async-programming-in-node-js/node
本章我将指导你学习异步编程的原理,并向你展现如何在 JavaScript 和 Node.js 中实现异步编程。git
在传统编程实践中,大多数 I/O 操做都是同步发生的。若是想一想 Java,想一想如何用 Java 读取一个文件,你会获得下面这样的代码:github
try(FileInputStream inputStream = new FileInputStream("foo.txt")) { Session IOUtils; String fileContent = IOUtils.toString(inputStream); }
这背后发生了什么?主线程会被阻塞,直到文件读完,这意味着读文件的同时其它什么事情都作不了。要解决此问题,更好利用 CPU,就不得不手动管理线程。npm
若是有更多阻塞操做,那么事件队列就变得更糟糕:编程
(红色块表示在进程等待外部资源的响应而被阻塞时,黑色块表示在代码运行时,绿色块表示应用的其他部分)数组
为解决这个问题,Node.js 引入了一种异步编程模型。服务器
异步 I/O 是一种输入/输出处理的形式,它容许在传输完成以前,其它处理能继续进行。网络
在以下的示例中,我将展现 Node.js 中一个简单的文件读写过程 - 同时采用同步和异步的方式,目的是向你展现经过避免阻塞应用程序,能实现什么。app
下面咱们先从一个简单的示例开始 - 以同步的方式用 Node.js 读一个文件:异步
const fs = require('fs') let content try { content = fs.readFileSync('file.md', 'utf-8') } catch (ex) { console.log(ex) } console.log(content)
这里刚刚发生了什么?咱们试图用 fs
模块的同步接口读一个文件。它按预期方式工做 - content
变量会包含 file.md
的内容。这种方式的问题是,Node.js 会被阻塞,直到操做完成 - 也就是说在文件正在被读取时,它什么事都作不了。
下面咱们看看如何修复!
咱们直到,在 JavaScript 中,异步编程只能用函数这个该语言的一等公民来实现:函数能够像全部其它变量同样传给其它函数。将其它函数做为参数的函数被称为高阶函数。
以下是一个高阶函数的最简单示例:
const numbers = [2,4,1,5,4] function isBiggerThanTwo (num) { return num > 2 } numbers.filter(isBiggerThanTwo)
在上例中,咱们将一个函数传递给 filter 函数。经过这种方式咱们能够定义过滤的逻辑。
这就是回调诞生的方式:若是你把一个函数传递给另外一个函数做为参数,那么就能够在另外一个函数完成任务时,在该函数内调用传进来的函数。不须要返回值,只用值调用另外一个函数。
这些所谓错误优先(error-first)的回调是 Node.js 自己的核心 - 核心模块用了它,大多数 NPM 中的模块也是。
const fs = require('fs') fs.readFile('file.md', 'utf-8', function (err, content) { if (err) { return console.log(err) } console.log(content) })
这里要注意:
错误处理: 必须在回调中检测错误,而不是用 try-catch
块。
没有返回值: 异步函数不返回值,可是值将被传递给回调。
下面咱们对这个文件作点修改,看看它其实是如何工做的:
const fs = require('fs') console.log('start reading a file...') fs.readFile('file.md', 'utf-8', function (err, content) { if (err) { console.log('error happened during reading the file') return console.log(err) } console.log(content) }) console.log('end of the file')
这段脚本的输出将是:
start reading a file... end of the file error happened during reading the file
正如你所见,一旦咱们开始读文件,执行继续,应用程序打印出 end of the file
。一旦文件读取完成,咱们的回调就只被调用一次。这怎么可能呢?迎接事件循环。
事件循环是 Node.js / JavaScript 的核心 - 它负责安排异步操做。
在深刻了解以前,要确保理解什么是事件驱动的编程。
事件驱动的编程是一种编程范式,在这种范式中程序流程是由事件决定的,好比用户行为(鼠标点击、按键)、传感器输出或者其它程序/线程的消息。
实际上,它意味着应用程序按照事件行事。
而且,咱们在第一章已经学过,从开发者的观点看,Node.js 是单线程的。这意味着没必要处理线程和线程同步,Node.js 远离了这种复杂性。除了你的代码,全部东西都是并行执行的。
要更深刻理解事件循环,请继续 youtube 上的视频。
至此你已经对 JavaScript 中的异步编程工做机制有了一个基本认识,下面咱们来看看几个如何组织代码的示例。
为避免所谓回调地狱,能够作的一件事情是开始使用 async.js。
Async.js 帮助组织应用程序结构,让流程控制更容易。
下面咱们看一个使用 Async.js 的简短示例,而后用 Promises 重写。
以下的代码片断映射三个文件上的状态:
async.parallel(['file1', 'file2', 'file3'], fs.stat, function (err, results) { // 如今结果是是一个每一个文件的状态数组 })
Promise 对象用于延迟及异步计算。一个 Promise 表明尚未完成,可是将来会执行的操做。
在实践中,前面的示例能够重写为以下:
function stats (file) { return new Promise((resolve, reject) => { fs.stat(file, (err, data) => { if (err) { return reject (err) } resolve(data) }) }) } Promise.all([ stats('file1'), stats('file2'), stats('file3') ]) .then((data) => console.log(data)) .catch((err) => console.log(err))
固然,若是使用一个有 Promise 接口的方法,那么 Promise 示例的行数也会少不少。
下一章将会学习如何启动第一个 Node.js HTTP 服务器。