专栏首页京程一灯有效使用 Node.js 事件循环

有效使用 Node.js 事件循环

对于 Node.js 应用程序开发新手而言,作为学习曲线的一部分,他们需要了解单线程事件循环的工作原理,以及它可能导致意外结果的方式。您可以使用本教程中的 3 个交互式示例中的事件循环进行练习。您很快就能编写快速、高效的代码来轻松处理异步调用。

我们将通过 3 段简单的代码段来演示事件循环的工作原理。

示例 1:一个简单示例

第一个示例定义了 3 个函数并调用了它们。单运行该代码。然后尝试更改 setTimeout() 调用中的数字值,以查看输出有何变化。例如,将所有值都设置为 0。

function printEventually(message) {

setTimeout(function () {console.log(message);}, 200);

}

function printSoon(message) {

setTimeout(function () {console.log(message);}, 100);

}

function printNow(message) {

console.log(message);

}

printEventually('world!');

printNow('Hello');

printSoon('there,');

您可能期望初始示例生成以下输出:

world!

Hello

there,

但是,实际的输出并不是这样的。printNow('Hello') 最先执行,然后是 printSoon('there,'),最后是 printEventually('world!')。这是因为当 Node 引擎在 printEventually() 调用中看到 setTimeout() 时,会将它转交给操作系统,然后继续调用 printNow()。该代码是同步的,所以消息 Hello 会立即打印出来。最后,对 printSoon() 中的 setTimeout() 的调用被转交给操作系统。一秒后(printSoon() 等待 1000 毫秒),printSoon() 中的 setTimeout() 计时器过期,因此会将消息 there, 打印到控制台。再过一秒后,最后一个 setTimeout() 过期,world! 显示出来。因此,3 个语句按以下顺序处理:

Hello

there,

world!

事件循环的工作原理

传统 Web 服务器是多线程的,每个会话通常都有自己的线程。该方法很有效,但当会话空闲时,它会要求 Web 服务器分配未被使用的资源。这些空闲会话的开销,使得扩展服务器来处理需求峰值变得更加困难。

另一方面,Node 引擎包含一个线程,用于应对操作系统发出的所有事件处理通知。如果该操作是异步的(例如,调用数据库或 REST 接口),Node 引擎会要求操作系统在准备好处理调用时通知它(比如在数据从数据库或 REST 调用传来时)。在此期间,Node 事件循环会前进到需要执行的下一个操作。

您需要了解,Node 引擎会立即处理每个操作。在一些情况下,“立即” 意味着要求操作系统在某个操作准备好处理时获知此事。

示例 2:回调模式

尽管第一个示例演示了 Node 如何处理异步代码,但您通常会采用回调模式 来调用异步代码。该模式如下所示:

清单 1. 定义异步函数的伪代码

function asyncCode(arg1, arg2, callback) {

// Do whatever the function does here. When it's complete, it should

// invoke the callback function, returning a (hopefully null)

// JavaScript Error object and the results of this function.

return callback(error, results);

}

传递给 asyncCode() 的最后一个参数是另一个函数。当 asyncCode() 完成其工作时,它会调用传递给它的回调函数。根据惯例,异步函数会将一个 JavaScript Error 对象作为第一个参数传递给回调,然后传递异步函数生成的结果。请注意,asyncCode() 函数可以拥有它所需要的任意多个参数,而且它可以将任意多个必要参数传递给回调函数。

这就是定义异步函数的方式。下面给出了调用异步函数的代码:

清单 2. 调用异步函数的伪代码

asyncCode(x, 37, function(error, results) {

if (error != null)

// Handle the error if there is one

else

// Otherwise do whatever we want with the results

});

这个代码版本使用了回调函数。按原样运行该代码。然后尝试更改 printMessage() 调用中的数字值,以查看输入有何变化。尝试将 console.log('Hello') 替换为对 printMessage() 的另一次调用。如果 timeout 值为 0,会发生什么?这是否会影响执行顺序?

function printMessage(timeout, message, callback) {

var error;

setTimeout(function () {return callback(error, message);}, timeout);

}

printMessage(200, 'world!', function(error, message) {

if (error != null)

console.error('Something went wrong!');

else

console.log(message);

});

console.log('Hello');

printMessage(100, 'there,', function(error, message) {

if (error != null)

console.error('Something went wrong!');

else

console.log(message);

});

printMessage() 函数将会实现回调模式。它设置了一个超时,因此 Node 会将该超时传递给操作系统。然后,Node 继续执行下一个操作。在本例中,下一个操作是对 console.log() 的一次简单调用。然后是对 printMessage() 的另一次调用,这次调用会设置另一个超时。超时过期时代码结束运行,并将 there, 和 world! 写入到控制台。回调函数生成了与第一个示例相同的消息:

Hello

there,

world!

示例 3:嵌套回调

如果出于某种原因,您想要按特定顺序打印消息中的 3 个单词,则需要嵌套这些回调函数。例如,如果 timeout 参数是 0 和 5000 之间随机生成的数字,那么您就无法知道将获得什么消息。

按原样运行该代码。现在尝试更改 printMessage() 调用中的数字值。无论您使用什么值,该代码都会按相同顺序执行。

function printMessage(timeout, message, callback) {

var error;

setTimeout(function () {return callback(error, message);}, timeout);

}

printMessage(200, 'world!', function(error, message) {

console.log(message);

printMessage(0, 'Hello', function(error, message) {

console.log(message);

printMessage(100, 'there,', function(error, message) {

console.log(message);

});

});

});

此代码确保对 printMessage() 的这 3 次调用是按特定顺序进行的。对 printMessage() 的第一次调用传入了一个也称为 printMessage() 的回调函数,该回调函数随后传入了另一个称为 printMessage() 的回调函数。该代码生成以下混乱的问候语:

world!

Hello

there,

该代码相对容易理解,因为我们忽略了错误处理,在再次调用 printMessage() 前只有一行代码。如果将错误处理添加回代码中,并在调用之间形成复杂的逻辑,这很快就会造成回调噩梦,导致代码嵌套多层且难以理解。

结束语

我们快速查看了如何使用 Node.js 单线程事件循环。使用 Node 库来访问数据库和文件等对象时,了解如何处理异步方法 — 和如何确保代码按一定的顺序执行 — 是至关重要的技能。随着对事件循环的深入理解,您就能编写快速、高效的代码来轻松处理异步调用。


小手一抖,资料全有。长按二维码关注京程一灯,阅读更多技术文章和业界动态。

本文分享自微信公众号 - 京程一灯(jingchengyideng),作者:Doug Tidwell

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-07-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JavaScript 中的代理对象

    JavaScript 支持 setter 和 getter 已经很长时间了。他们用带有 set 和 get 关键字的简单语法来拦截对象的属性访问和值的修改操作...

    疯狂的技术宅
  • 用 Vue 开发自己的 Chrome 扩展

    浏览器扩展程序是可以修改和增强 Web 浏览器功能的小程序。它们可用于各种任务,例如阻止广告,管理密码,组织标签,改变网页的外观和行为等等。

    疯狂的技术宅
  • Vue.js 中的片段

    为了使诸如屏幕阅读器之类的辅助技术能够解释网页和应用程序,无障碍支持是必需的。为了使这些技术起作用,开发人员需要考虑可访问性。

    疯狂的技术宅
  • Hexo + Serverless Framework,简单三步搭建你的个人博客

    很多人都想拥有自己的个人博客,还得看起来漂亮、酷酷的。尤其对开发者来说,不仅可以分享技术(装)心得(逼),面试的时候还能成为加分。这里介绍两款好用的神器,不用...

    腾讯云serverless团队
  • CDN如何给子账号授权预热权限

    内容分发网络(CDN)接入了腾讯云云资源访问管理(Cloud Access Management)系统,您可以在 访问管理 控制台进行用户组、用户、角色、策略等...

    用户2553168
  • Python快速切换不同版本

    Hyperledger目前只支持2.7,但是3.6明显对编码解析更好。 所以只好找个快速切换版本的办法了。。。

    SeanCheney
  • 新学期师生自救指南!我真的太难了......

    ? 今天 鹅老师要跟大家剧透即将上映的几部电影 ▼ 史诗级巨制灾难大片《开学》 青春剧《匆匆暑假》、苦情剧《不舍得懒觉》 悬疑剧《谁偷走了我的暑假作业》 快乐...

    鹅老师
  • iOS界面设计,12个优秀案例激发你的灵感

    总所周知,iOS和Android是当今两大移动平台,前者采用Human Interface Design,后者采用Material Design。作为设计师,尤...

    奔跑的小鹿
  • 使用async,await关键字进行API Access Token的获取

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    Jerry Wang
  • 使用安卓手机远程linux云主机

    1、手机浏览器下载安装JuiceSSH(如果有些应用商店可以找到这个app,可以在应用商店下载安装)

    cdc

扫码关注云+社区

领取腾讯云代金券