写点什么

NodeJs 深入浅出之旅:异步 I/O (上)🐋

用户头像
空城机
关注
发布于: 3 小时前
NodeJs深入浅出之旅:异步I/O (上)🐋

异步 I/O

在前端,最经典的异步就是 Ajax


文章介绍:《高性能 JavaScriptの六 -- 老生常谈 Ajax》


Node 是首个大规模将异步 I/O 应用在应用层上的平台,力求在单线程上将资源分配的更高效

为了弥补单线程无法利用多核 CPU 的缺点,Node 提供了类似前端浏览器中 Web Workers 的子进程,子进程可以通过工作进程高效的利用 CPU 和 I/O


Node 异步 I/O 基本要素:

  • 事件循环:

  • 观察者:

  • 请求对象

  • I/O 线程池


事件循环是一个典型的生产者/消费者模型。 异步 I/O、网络请求是事件生产者,为 Node 提供不同类型的事件,事件被传递到对应的观察者那里,事件循环从观察者那取出事件并处理


官方介绍:http://nodejs.cn/learn/the-nodejs-event-loop


异步流程


node 中使用 import: https://blog.csdn.net/goutinga/article/details/108074649


测试代码:

import { nextTick } from 'process';
module.exports = function (app) { nextTick(() => console.log(1)); // 优先级第二 setImmediate(() => console.log(2)); // 优先级最低 Promise.resolve().then(() => console.log(3)); // 优先级第三 queueMicrotask(() => console.log(4)); // 优先级第四 setTimeout(() => console.log(6), 0); // 优先级第五 console.log(5) // 优先级最高}
复制代码


输出的顺序:5 1 3 4 6 2


这里可以发现,nextTick中回调函数的执行优先级最高,而setImmediate的执行优先级最低。


原因在于事件循环对观察者的检查是有先后顺序的, nextTick属于 idle 观察者, setImmediate属于 check 观察者。 idle 观察者先于 I/O 观察者, I/O 观察者先于 check 观察者


异步编程 🐟

高阶函数

在通常的语言中,函数的参数只接受基本的数据类型或是对象引用,返回值也只是基本数据类型和对象引用。


基本函数:

function foo(x) {     return x;}
复制代码


高阶函数是可以把函数作为参数,或是将函数作为返回值


高阶函数:

function foo(x) {    return function () {        return x;    };}
复制代码

偏函数

偏函数用法指创建一个调用另外一个部分(参数或变量已经预置的函数)的函数用法


举个粒子, 定义判断类别的方法:

var toString = Object.prototype.toString;
var isString = function(obj) { return toString.call(obj) == '[object String]';}var isFunction = function(obj) { return toString.call(obj) == '[object Function]';}
复制代码


如果需要重复定义一些相似的函数,这样代码就会冗余。 为了解决重复定义的问题,可以创建一个新函数:

var isType = function(type) {    return function(obj) {        return toString.call(obj) == '[object ' + type + ']';    }}
var isString = isType('String');var isFunction = isType('Function');
复制代码


这种通过指定部分参数来产生一个新的定制函数的形式就是偏函数



异常处理

在普通异常处理当中,通常使用try/catch/final语句块进行异常捕获。


但是这在异步编程中并不一定使用。 异步 I/O 实现主要包涵在两个阶段:1、提交请求 2、处理结果。


这两个阶段中间有事件循环的调度,所以彼此不关联

graph LR;    提交请求 --> 事件循环 --> 处理结果
复制代码


Node 在处理异常上形成了一种约定,将异常作为回调函数第一个实参传回,如果为控制,则表明异步调用没有异常抛出。


JavaScript 的 try…catch 机制不能用来截获异步方法产生的错误。新手的常见错误之一是试图在错误优先回调函数中使用 throw


参考:《错误优先的回调》


错误例子:

const fs = require('fs');
try { fs.readFile('/some/file/that/does-not-exist', (err, data) => { // 错误的假设:在这里抛出错误。 if (err) { throw err; } });} catch (err) { // 这里不会截获回调函数中的 throw。 console.error(err);}
复制代码


会出现报错:





正确例子:

function errorFirstCallback(err, data) {    if (err) {      console.error('出错', err);      return;    }    console.log(data);}fs.readFile('./src/b.txt', errorFirstCallback)
复制代码



异步编程解决方案 🐳

异步编程主要解决方案有三种


  • 事件发布/订阅模式

  • Promise/Deferred 模式

  • 流程控制库

事件发布/订阅模式

事件监听器模式是一种广泛用于异步编程的模式,是回调函数的事件化,又称发布/订阅模式。


Node 自身提供的 events 事件触发器模块是发布/订阅模式的一个简单实现。


所有触发事件的对象都是 EventEmitter 类的实例。 这些对象暴露了 eventEmitter.on() 函数,允许将一个或多个函数绑定到对象触发的命名事件。 通常,事件名称是驼峰式字符串,但也可以使用任何有效的 JavaScript 属性键。


EventEmitter 对象触发事件时,所有绑定到该特定事件的函数都会被同步地调用。 被调用的监听器返回的任何值都将被忽略和丢弃。


以下示例展示了使用单个监听器的简单的 EventEmitter 实例。 eventEmitter.on() 方法用于注册监听器,eventEmitter.emit() 方法用于触发事件

简单例子

const { EventEmitter, errorMonitor } = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();// 订阅myEmitter.on('event1', (val) => { console.log(val);});// 发布myEmitter.emit('event1', 'hello world!');
复制代码


结果:在订阅中输出 hello world!


订阅函数就是一个高阶函数的应用, 事件发布/订阅模式可以实现一个事件与多个回调函数的关联,这些回调函数又被称为<font color=#CE5CEA size=2 >事件监听器</font>


通过emit()发布事件后,消息回立即传递给当前事件的所有监听器执行,监听器可以添加和删除。


在 Node 中, emit()调用多半是伴随事件循环而异步触发的。

监听器过多警告

注意:如果一个事件添加了超过 10 个监听器,将会得到一条警告。 并且事件相关的监听器过多,可能存在过多占用 CPU 的情景。

for (let i = 0; i < 11; i++) {    myEmitter.on('event1', (val) => {        console.log(i);    });}// 发布myEmitter.emit('event1', 'hello world!');
复制代码



如果想要去除警告,调用setMaxListeners(0);可以去除限制


myEmitter.setMaxListeners(0); 添加在 for 循环前即可

发布于: 3 小时前阅读数: 3
用户头像

空城机

关注

曾经沧海难为水,只是当时已惘然 2021.03.22 加入

业余作者,在线水文 主要干前端的活,业余会学学python 欢迎各位关注,互相学习,互相进步

评论

发布
暂无评论
NodeJs深入浅出之旅:异步I/O (上)🐋