Node.js 中的异步编程

由于 JavaScript 是单线程运行的,如果单线程的所有程序都是同步执行的,那么一旦某段程序调用堵塞,整个线程就挂起了。所以 JavaScript 天生是异步的。

Node.js 使用的主要编程语言是 JavaScript,采用异步编程,其主要特点如下:

单线程相比多线程而已,最大的劣势就是无法充分使用利用多核 CPU。

1.但是单线程也避免了多线程中的存在的一些问题:线程的创建和上下文切换开销大以及多线程经常面临锁,状态同步问题。

2.而采用异步 I/O 编程,远离线程被外部调用所阻塞,可以充分使用单核CPU。

3.为了弥补单线程无法使用多核 CPU 的缺点,Node 提供了子工作进程的方式去高效使用 CPU。

4.最后我们在部署的服务的时候,同一台机器可以部署多个实例,可以充分利用 CPU。

Node.js 中异步原理

阻塞IO/非阻塞IO

什么叫 IO 呢?一般是指除了 CPU 之外的外部设备的任务都叫 I/O 操作。最常见的 I/O 操作类型就是文件操作和 TCP/UDP 网络操作
操作系统对计算机进行了抽象,将所有输入输出设备定义成文件,内核在进行 IO 操作时,通过文件描述符进行管理,应用程序在进行 IO 调用时,根据文件描述符去实现 IO 数据的读取,非阻塞 IO 和阻塞 IO 的区别在于,阻塞 IO 需要完成整个文件的读取过程,而非阻塞 IO 可以不带数据直接返回,然后再根据文件描述符区轮询查询返回数据。而 Node 正式利用非阻塞 IO 实现异步编程的。

异步 IO 原理

异步 IO 是指应用层以异步的方式去读取非阻塞 IO 的方式,只有非阻塞 IO 才能执行异步操作。Node 底层采用线程池的原理管理异步 IO,所以我们通常所的 单线程是指 Node 中 JavaScript 的执行是单线程的,但 Node 本身是多线程的。Node.js 中异步 IO 是通过事件循环的方式实现的,异步 IO 事件主要来源于网络请求和文件 IO。事件循环主要由以下几个部分实现:

1.事件循环:Node 启动进程后,便会创建一个类似 while 的循环,每次循环我们称为一个 tick,在循环的过程中,每次都要查看是否有事件需要处理,如果有,则取出处理,如果没有事件需要处理则直接退出。

2.观察者:每个事件循环中,会有一个多个观察者存在,事件循环的过程就是不断询问观察者有没有需要处理的事件的过程。

3.请求对象:请求对象是异步 IO 的中间产物,所有状态都保存在这个请求对象中,包括送入线程池以及 IO 操作完毕的回调处理,告诉观察者。

4.线程池:多个线程池按照一定的算法并发执行请求对象,执行完请求对象通知 IOCP 调用完成,通知观察者,放入观察者列表

而异步 IO 的事件调用模型在不同的操作系统上实现不一样,Linux 系统中是 epoll, 在 BSD 系统(MacOS)中是 kqueue, 在 Solaris 系统中是 event ports, 在 Windows 系统中是 IOCP(Input Output Completion Port)。但是 Node.js 使用 libuv 做统一封装,兼容所有平台的异步事件逻辑。

异步编程的分类
解决异步问题方法大致包括:直接回调、pub/sub模式(事件模式)、异步库控制库(例如async、when)、promise、Generator等。

回调函数
回调函数是常用的解决异步的方法,经常接触和使用到,易于理解,并且在库或函数中非常容易实现。这种也是大家接使用异步编程经常使用到的方法。

  但是回调函数的方式存在如下的问题:

 1. 可能形成万恶的嵌套金字塔,代码不易阅读;

 2. 只能对应一个回调函数,在很多场景中成为一个限制。

pub/sub模式(事件)
该模式也称为事件模式,是回调函数的事件化,在jQuery等类库中非常常见。

 事件发布订阅者模式本身并无同步与异步调用的问题,但是在node中,emit调用多半是伴随事件循环而异步触发的。该模式常用来解耦业务逻辑,事件发布者无须关注注册的回调函数,也不用关注回调函数的个数,数据通过消息的方式可以很灵活的传递。

 该模式的好处是:1. 便于理解;2. 不再局限于一个回调函数。

 不好的地方时:1. 需要借助类库; 2.事件与回调函数的顺序很重要

代码如下:

var img = document.querySelect(#id);

img.addEventListener('load', function() {

// 图片加载完成

......

});

img.addEventListener('error', function() {

// 出问题了

......

});

上述代码存在两个问题:

  **a. img实际已经加载完成,此时才绑定load回调函数,结果回调不会执行,但依然希望执行该对应回调函数。**

代码如下:

var img = document.querySelect(#id);

function load() {

...

}

if(img.complete) {

load();

} else {

img.addEventListener('load', load);

}

img.addEventListener('error', function() {

// 出问题了

......

});

b. 无法很好处理存在异常

  结论:事件机制最适合处理同一个对象上反复发生的事情,不需要考虑当绑定回调函数之前事件发生的情况。

异步控制库
目前的异步库主要有Q、when.js、win.js、RSVP.js等。

  这些库的特点是代码是线性的,可以从上到下完成书写,符合自然习惯。

  不好的地方也是风格各异,不便于阅读,增加学习成本。

Promise
Promise翻译成中文为承诺,个人理解是异步完成之后,就会给外部一个结果(成功或失败),并承诺结果不再发生改变。换句话就是Promise反应了一个操作的最终返回结果值(A promise represents the eventual value returned from the single completion of an operation)。目前Promise已经引入到ES6规范里面,Chrome、firefox等高级浏览器已经在内部实现了该原生方法,使用起来相当方便。

状态

 包含三种状态:pending、fulfilled、rejected,三种状态只能发生两种转换(从pending--->fulfilled、pending—>rejected),并且状态的转换仅能发生一次。

then方法

then方法用于指定异步事件完成之后的回调函数。

   这个方法可以说是Promise的灵魂方法,该方法让Promise充满了魔力。有如下几个具体表现:

    then方法返回Promise。这样就实现了多个异步操作的串行操作。

实现了多个不同异步库之间的转换。

      在异步中存在一个叫thenable的对象,就是指具有then方法的对象,只要一个对象对象具有then方法,就可以对其进行转换
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值