目录
非阻塞I/O
nodejs的非阻塞 I/O
事件驱动
异步编程
回调函数格式规范
异步流程控制
promise
then & .catch
promise解决异步流程控制
async/await
事件循环
总结
首页 web前端 js教程 总结分享了解nodejs的几个关键节点

总结分享了解nodejs的几个关键节点

Jul 06, 2022 pm 08:36 PM
nodejs nodejs​

总结分享了解nodejs的几个关键节点

本文是个人在实际开发和学习中对nodejs的一些理解,现整理出来方便日后查阅,如果能给您启发将不胜荣幸。

非阻塞I/O

I/O:即 Input / Output,一个系统的输入和输出。

一个系统可以理解为一个个体,比如说一个人,你说话就是输出,你听就是输入。

阻塞 I/O 与非阻塞 I/O 的区别就在于系统接收输入再到输出期间,能不能接收其他输入

下面以两个例子来说明什么是阻塞 I/O 和非阻塞 I/O:

1、打饭

1.png

首先我们要确定一个系统的范围,在这个例子中食堂阿姨和餐厅的服务生看成是一个系统,输入就是点菜,输出就是端菜

那么在点菜和端菜之间能不能接受其他人的点菜,就可以判断是阻塞I/O还是非阻塞I/O。

对于食堂阿姨,他在点菜的时候,是不能帮其他同学点菜的,只有这个同学点完菜端菜走了之后,才能接受下一个同学的点菜,所以食堂阿姨是阻塞I/O。

对于餐厅服务员,他可以在点完菜以后,这个客人端菜之前是可以服务下一位客人的,所以服务员是非阻塞I/O。

2、做家务

2.png

在洗衣服的时候,是不需要等着洗衣机旁边的,这个时候可以去扫地和整理书桌,当整理完书桌后衣服也洗好了,这个时候去晾衣服,那么总共只需要25分钟。

洗衣服其实就是一个非阻塞I/O,在把衣服扔进洗衣机和洗完衣服期间,你是可以干其他事情的。

非阻塞I/O之所以能提升性能,是因为它可以把不必要的等待给节省掉。

理解非阻塞I/O的要点在于

  • 确定一个进行I/O的系统边界。这非常关键,如果把系统扩大,上面餐厅的例子,如果把系统扩大到整个餐厅,那么厨师肯定是一个阻塞 I/O。
  • 在 I/O 过程中,能不能进行其他 I/O。

nodejs的非阻塞 I/O

nodejs的非阻塞 I/O 是怎么体现的呢?前面说过理解非阻塞 I/O 的一个重要点是先确定一个系统边界,node的系统边界就是主线程

如果下面的架构图按照线程的维护划分,左边虚线部分是nodejs线程,右边虚线部分是c++线程。

3.png

现在 nodejs 线程需要去查询数据库,这是一个典型的 I/O 操作,它不会等待 I/O 的结果,而且继续处理其他的操作,它会把大量的计算能力分发到其他的c++线程去计算。

等到结果出来后返回给nodejs线程,在获得结果之前nodejs 线程还能进行其他的I/O操作,所以是非阻塞的。

nodejs 线程 相当于左边部分是服务员,c++ 线程是厨师。

所以,node的非阻塞I/O是通过调用c++的worker threads来完成的。

那当 c++ 线程获取结果后怎么通知 nodejs 线程呢?答案是事件驱动

事件驱动

阻塞:I/O时进程休眠,等待I/O完成后进行下一步; 

非阻塞:I/O时函数立即返回,进程不等待I/O完成。

那怎么知道返回的结果,就需要用到事件驱动

所谓事件驱动可以理解为跟前端点击事件一样,我首先写一个点击事件,但是我不知道什么时候触发,只有触发的时候就去让主线程执行事件驱动函数。

这种模式也是一种观察者模式,就是我首先先监听这个事件,等触发时我就去执行。

那怎么实现事件驱动呢?答案是异步编程

异步编程

上面说过nodejs有大量的非阻塞I/O,那么非阻塞I/O的结果是需要通过回调函数来获取的,这种通过回调函数的方式,就是异步编程。比如下面的代码是通过回调函数获取结果的:

glob(__dirname+'/**/*', (err, res) => {
    result = res
    console.log('get result')
})
登录后复制

回调函数格式规范

nodejs的回调函数第一个参数是error,后面的参数才是结果。为什么要这么做呢?

try {
  interview(function () {
       console.log('smile')
  })
} catch(err) {
    console.log('cry', err)
}

function interview(callback) {
    setTimeout(() => {
        if(Math.random() < 0.1) {
            callback(&#39;success&#39;)
        } else {
            throw new Error(&#39;fail&#39;)
        }
    }, 500)
}
登录后复制

执行之后,没有被捕获,错误被扔到了全局,导致整个nodejs程序崩溃了。

4.png

没有被try catch捕获是因为setTimeout重新开启了事件循环,每开启一个事件循环就重新生一个调用栈context,try catch是属于上一个事件循环的调用栈的,setTimeout的回调函数执行的时候,调用栈都不一样了,在这个新的调用栈中是没有try catch,所以这个错误被扔到全局,无法捕获。具体可以参考这一篇文章异步队列进行try catch时的问题

那么怎么办呢?把错误也作为一个参数:

function interview(callback) {
    setTimeout(() => {
        if(Math.random() < 0.5) {
            callback(&#39;success&#39;)
        } else {
            callback(new Error(&#39;fail&#39;))
        }
    }, 500)
}

interview(function (res) {
    if (res instanceof Error) {
        console.log(&#39;cry&#39;)
        return
    } 
    console.log(&#39;smile&#39;)
})
登录后复制

但是这样就比较麻烦,在回调中还要判断,所以就产生一种约定成熟的规定,第一个参数是err,如果不存在表示执行成功。

function interview(callback) {
    setTimeout(() => {
        if(Math.random() < 0.5) {
            callback(null, &#39;success&#39;)
        } else {
            callback(new Error(&#39;fail&#39;))
        }
    }, 500)
}

interview(function (res) {
    if (res) {
        return
    } 
    console.log(&#39;smile&#39;)
})
登录后复制

异步流程控制

nodejs的回调写法,不仅会带来回调地域,还会带来异步流程控制的问题。

异步流程控制主要是指当并发的时候,怎么来处理并发的逻辑。还是上面的例子,如果你同事面试两家公司,只有当成功面试两家的时候,才可以不面试第三家,那么怎么写这个逻辑呢?需要全局顶一个一个变量count:

var count = 0
interview((err) => {
    if (err) {
        return
    }
    count++
    if (count >= 2) {
        // 处理逻辑
    }
})

interview((err) => {
    if (err) {
        return
    }
    count++
    if (count >= 2) {
        // 处理逻辑
    }
})
登录后复制

像上面这种写法就非常麻烦,且难看。所以,后来就出现了promise,async/await的写法。

promise

当前事件循环得不到的结果,但未来的事件循环会给你结果。很像一个渣男说的话。

promise不仅是一个渣男,还是一个状态机:

  • pending
  • fulfilled/resolved
  • rejectd
const pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('2')
    }, 200)
})
console.log(pro) // 打印:Promise { <pending> }
登录后复制

then & .catch

  • resolved 状态的 promise 会调用后面的第一个 then
  • rejected 状态的 promise 会调用后面的第一个 catch
  • 任何一个 reject 状态且后面没有 .catch 的 promise,都会造成浏览器或者 node 环境的全局错误。uncaught 表示未捕获的错误。

5.png

执行then或者catch会返回一个新的promise,该promise最终状态根据then和catch的回调函数的执行结果决定:

  • 如果回调函数始终是throw new Error,该promise是rejected状态
  • 如果回调函数始终是return,该promise是resolved状态
  • 但如果回调函数始终是return一个promise,该promise会和回调函数return的promise状态保持一致
function interview() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve('success')
            } else {
                reject(new Error('fail'))
            }
        })
    })
}

var promise = interview()
var promise1 = promise.then(() => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('accept')
        }, 400)
    })
})
登录后复制

promise1的状态是由return里面的promise的状态决定的,也就是return里面的promise执行完后的状态就是promise1的状态。这样有什么好处呢?这样可以解决回调地狱的问题

var promise = interview()
    .then(() => {
        return interview()
    })
    .then(() => {
        return interview()
    })
    .then(() => {
        return interview()
    })
    .catch(e => {
        console.log(e)
    })
登录后复制

then如果返回的promise的状态是rejected,那么会调用后面第一个catch,后面的then就不会在调用了。记住:rejected调用后面的第一个catch,resolved调用后面的第一个then。

promise解决异步流程控制

如果promise仅仅是为了解决地狱回调,太小看promise了,promise最主要的作用是解决异步流程控制问题。下面如果要同时面试两家公司:

function interview() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve('success')
            } else {
                reject(new Error('fail'))
            }
        })
    })
}

promise
    .all([interview(), interview()])
    .then(() => {
        console.log('smile')
    })
    // 如果有一家公司rejected,就catch
    .catch(() => {
        console.log('cry')
    })
登录后复制

async/await

sync/await到底是什么:

console.log(async function() {
    return 4
})

console.log(function() {
    return new Promise((resolve, reject) => {
        resolve(4)
    })
})
登录后复制

打印的结果一样,也就是async/await是promse的语法糖而已。

我们知道try catch捕获错误是依赖调用栈的,只能捕获到调用栈以上的错误。但是如果使用await后能捕捉到调用栈所有函数的错误。即便这个错误是在另一个事件循环的调用栈抛出的,比如setTimeout。

改造面试代码,可以看到代码精简了很多。

try {
    await interview(1)
    await interview(2)
    await interview(2)
} catch(e => {
    console.log(e)
})
登录后复制

如果是并行任务呢?

await Promise.all([interview(1), interview(2)])
登录后复制

事件循环

因为nodejs的非阻塞 I/0, 所以需要利用事件驱动的方式获取 I/O 的结果,实现事件驱动拿到结果必须使用异步编程,比如回调函数。那么如何来有序的执行这些回调函数来获取结果呢?那就需要使用事件循环。

事件循环是实现 nodejs 非阻塞 I/O 功能的关键基础,非阻塞I/O和事件循环都是属于 libuv 这个c++库提供的能力。

6.png

代码演示:

const eventloop = {
    queue: [],
    loop() {
        while(this.queue.length) {
            const callback = this.queue.shift()
            callback()
        }
        setTimeout(this.loop.bind(this), 50)
    },
    add(callback) {
        this.queue.push(callback)
    }
}

eventloop.loop()

setTimeout(() => {
    eventloop.add(() => {
        console.log('1')
    })
}, 500)

setTimeout(() => {
	eventloop.add(() => {
		console.log('2')
	})
}, 800)
登录后复制

setTimeout(this.loop.bind(this), 50)保证了50ms就会去看队列中是否有回调,如果有就去执行。这样就形成了一个事件循环。

当然实际的事件要复杂的多,队列也不止一个,比如有一个文件操作对列,一个时间对列。

const eventloop = {
    queue: [],
    fsQueue: [],
    timerQueue: [],
    loop() {
        while(this.queue.length) {
            const callback = this.queue.shift()
            callback()
        }
        this.fsQueue.forEach(callback => {
            if (done) {
                callback()
            }
        })
        setTimeout(this.loop.bind(this), 50)
    },
    add(callback) {
        this.queue.push(callback)
    }
}
登录后复制

总结

首先我们弄清楚了什么是非阻塞I/O,即遇到I/O立刻跳过执行后面的任务,不会等待I/O的结果。当I/O处理好了之后就会调用我们注册的事件处理函数,这就叫事件驱动。实现事件驱动就必须要用异步编程,异步编程是nodejs中最重要的环节,它从回调函数到promise,最后到async/await(使用同步的方法写异步逻辑)。

更多node相关知识,请访问:nodejs 教程

以上是总结分享了解nodejs的几个关键节点的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

nodejs和vuejs区别 nodejs和vuejs区别 Apr 21, 2024 am 04:17 AM

Node.js 是一种服务器端 JavaScript 运行时,而 Vue.js 是一个客户端 JavaScript 框架,用于创建交互式用户界面。Node.js 用于服务器端开发,如后端服务 API 开发和数据处理,而 Vue.js 用于客户端开发,如单页面应用程序和响应式用户界面。

nodejs是后端框架吗 nodejs是后端框架吗 Apr 21, 2024 am 05:09 AM

Node.js 可作为后端框架使用,因为它提供高性能、可扩展性、跨平台支持、丰富的生态系统和易于开发等功能。

nodejs怎么连接mysql数据库 nodejs怎么连接mysql数据库 Apr 21, 2024 am 06:13 AM

要连接 MySQL 数据库,需要遵循以下步骤:安装 mysql2 驱动程序。使用 mysql2.createConnection() 创建连接对象,其中包含主机地址、端口、用户名、密码和数据库名称。使用 connection.query() 执行查询。最后使用 connection.end() 结束连接。

nodejs中的全局变量有哪些 nodejs中的全局变量有哪些 Apr 21, 2024 am 04:54 AM

Node.js 中存在以下全局变量:全局对象:global核心模块:process、console、require运行时环境变量:__dirname、__filename、__line、__column常量:undefined、null、NaN、Infinity、-Infinity

nodejs安装目录里的npm与npm.cmd文件有什么区别 nodejs安装目录里的npm与npm.cmd文件有什么区别 Apr 21, 2024 am 05:18 AM

Node.js 安装目录中有两个与 npm 相关的文件:npm 和 npm.cmd,区别如下:扩展名不同:npm 是可执行文件,npm.cmd 是命令窗口快捷方式。Windows 用户:npm.cmd 可以在命令提示符下使用,npm 只能从命令行运行。兼容性:npm.cmd 特定于 Windows 系统,npm 跨平台可用。使用建议:Windows 用户使用 npm.cmd,其他操作系统使用 npm。

nodejs和java的差别大吗 nodejs和java的差别大吗 Apr 21, 2024 am 06:12 AM

Node.js 和 Java 的主要差异在于设计和特性:事件驱动与线程驱动:Node.js 基于事件驱动,Java 基于线程驱动。单线程与多线程:Node.js 使用单线程事件循环,Java 使用多线程架构。运行时环境:Node.js 在 V8 JavaScript 引擎上运行,而 Java 在 JVM 上运行。语法:Node.js 使用 JavaScript 语法,而 Java 使用 Java 语法。用途:Node.js 适用于 I/O 密集型任务,而 Java 适用于大型企业应用程序。

nodejs是后端开发语言吗 nodejs是后端开发语言吗 Apr 21, 2024 am 05:09 AM

是的,Node.js 是一种后端开发语言。它用于后端开发,包括处理服务器端业务逻辑、管理数据库连接和提供 API。

nodejs项目怎么部署到服务器 nodejs项目怎么部署到服务器 Apr 21, 2024 am 04:40 AM

Node.js 项目的服务器部署步骤:准备部署环境:获取服务器访问权限、安装 Node.js、设置 Git 存储库。构建应用程序:使用 npm run build 生成可部署代码和依赖项。上传代码到服务器:通过 Git 或文件传输协议。安装依赖项:SSH 登录服务器并使用 npm install 安装应用程序依赖项。启动应用程序:使用 node index.js 等命令启动应用程序,或使用 pm2 等进程管理器。配置反向代理(可选):使用 Nginx 或 Apache 等反向代理路由流量到应用程

See all articles