nodejs知识点,node.js和node
node.js速度课程简介:进入学习
本文是nodejs在实际开发和学习中的个人理解。现在整理出来备查,如果能对你有所启发,将不胜荣幸。
非阻塞I/O
I/O:输入/输出,系统的输入和输出。
系统可以理解为一个个体,例如,一个人。你的讲话是输出,你的倾听是输入。
阻塞I/O和非阻塞I/O的区别是系统接收输入再到输出期间,能不能接收其他输入。
下面两个例子用来说明什么是阻塞I/O和非阻塞I/O:
1、打饭
首先,我们要确定一个系统的范围。在这个例子中,食堂的阿姨和餐厅的服务员被视为一个系统,输入就是点菜,输出就是端菜。
那么在点餐和上菜之间是否可以接受别人的订单,就可以判断是阻塞I/O还是非阻塞I/O。
至于食堂的阿姨,她点菜的时候不能帮其他同学点菜。只有这个同学点完菜走了,她才能接受下一个同学的订单,所以食堂阿姨在堵I/O。
对于一个餐厅服务员来说,点餐后可以服务下一位客人,所以服务员是非阻塞I/O。
2、做家务
洗衣服的时候,不需要等洗衣机。这个时候,你可以扫地,整理书桌。做完书桌后,衣服就洗了。这个时候就可以挂衣服了,所以总共只需要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线程。
现在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)={
结果=分辨率
console.log(获取结果)
})
回调函数格式规范
nodejs的回调函数第一个参数是error,后面的参数才是结果。为什么要这么做?
尝试{
面试(函数(){
console.log(微笑)
})
} catch(err) {
console.log(cry ,err)
}
职能面试(回访){
setTimeout(()={
if(Math.random() 0.1) {
回拨(“成功”)
}否则{
抛出新错误(“失败”)
}
}, 500)
}执行后没有被抓住,错误丢到全世界,导致整个nodejs程序崩溃。
它未被try catch捕获,因为setTimeout重新打开了事件循环,并且每次打开事件循环时,都会重新生成调用堆栈上下文。try catch属于上一个事件循环的调用堆栈。当setTimeout的回调函数被执行时,调用栈都是不同的。这个新的调用堆栈中没有try catch,所以这个错误被抛给了全世界,无法被捕获。详情请参考本文尝试try catch时异步队列的问题。
那我该怎么办呢?将误差作为一个参数:
职能面试(回访){
setTimeout(()={
if(Math.random() 0.5) {
回拨(“成功”)
}否则{
回调(新错误( fail ))
}
}, 500)
}
面试(职能(职责){
if (res instanceof Error) {
console.log(哭泣)
返回
}
console.log(微笑)
})但是这个比较麻烦,要在回调中判断,所以有一个成熟的规定。第一个参数是err,如果不存在,表示执行成功。
职能面试(回访){
setTimeout(()={
if(Math.random() 0.5) {
回调(空,“成功”)
}否则{
回调(新错误( fail ))
}
}, 500)
}
面试(职能(职责){
if (res) {
返回
}
console.log(微笑)
})
异步流程控制
nodejs的回调写不仅会带来回调区,还有异步流程控制的问题。
异步流程控制主要是指并发时如何处理并发的逻辑。上面的例子是一样的。如果你的同事面试了两家公司,只有你成功面试了两家公司,你才能不面试第三家。那么这个逻辑怎么写呢?需要全局顶级变量计数:
var计数=0
面试((呃)={
如果(错误){
返回
}
数数
if (count=2) {
//处理逻辑
}
})
面试((呃)={
如果(错误){
返回
}
数数
if (count=2) {
//处理逻辑
}
})像上面这样写,很麻烦,也很难看。所以后来出现了promise,async/await的写法。
promise
诺言不仅是渣男,也是国家机器:
pending fulfilled/resolvedrejectdconst pro=new Promise((resolve,reject)={
setTimeout(()={
解析( 2 )
}, 200)
})
console . log(pro)//Print:promise { pending }
then .catch
处于已解决状态的承诺将调用处于已拒绝状态的第一个承诺将调用处于已拒绝状态的第一个承诺,并且不调用处于已拒绝状态的任何承诺。catch将在浏览器或节点环境中导致全局错误。未计数表示未捕获的错误。
Execute then或catch将为返回一个新的promise,承诺的最终状态将根据then和catch的回调函数的执行结果来确定:
如果回调函数总是抛出新的错误,则承诺被拒绝;如果回调函数总是return,则承诺被解析;但如果回调函数总是return,则为promise会和回调函数return的promise状态保持一致。功能面试(){
返回新承诺((解决,拒绝)={
setTimeout(()={
if (Math.random() 0.5) {
解决(“成功”)
}否则{
拒绝(新错误(失败))
}
})
})
}
var promise=面试()
var promise1=promise.then(()={
返回新承诺((解决,拒绝)={
setTimeout(()={
解决(“接受”)
}, 400)
})
})承诺1的状态由回报承诺的状态决定,即回报承诺执行后的状态为承诺1的状态。这有什么好处?没关系。解决回调地狱的问题.
var promise=面试()。然后(()={
回访()
})。然后(()={
回访()
})。然后(()={
回访()
})。catch(e={
console.log(e)
})那么如果返回的承诺的状态是拒绝,那么第一个catch就会被调用,然后下面的就不会再被调用了。记住:拒绝的调用是第一个捕获的,解决的调用是第一个捕获的。
promise解决异步流程控制
如果承诺只是为了解决地狱回调,那就太小看承诺了。promise的主要功能是解决异步过程控制问题。如果你想同时面试两家公司:
功能面试(){
返回新承诺((解决,拒绝)={
setTimeout(()={
if (Math.random() 0.5) {
解决(“成功”)
}否则{
拒绝(新错误(失败))
}
})
})
}
承诺。全部([面试()、面试()])。然后(()={
console.log(微笑)
})
//如果一个公司被拒绝了,就抓住它。catch(()={
console.log(哭泣)
}):
async/await
到底什么是同步/等待:
console.log(异步函数(){
返回4
})
console.log(function() {
返回新承诺((解决,拒绝)={
解决(4)
})
})打印的结果是一样的,就是async/await只是promse的语法糖。
我们知道try catch捕获的错误是依赖调用栈,只能捕获调用栈以上的错误。但是,如果您使用await,您可以捕获调用堆栈中所有函数的错误。即使此错误是在另一个事件循环(如setTimeout)的调用堆栈上引发的。
通过修改面试代码,可以看到代码精简了很多。
尝试{
等待面试(1)
等待面试(2)
等待面试(2)
} catch(e={
console.log(e)
})如果是并行任务呢?
Awaitpromise.all ([interview (1),interview (2)]):
事件循环
由于nodejs的非阻塞I/O,所以需要以事件驱动的方式获取I/O的结果。异步编程,如回调函数,必须通过实现事件驱动来得到结果。那么如何执行这些回调函数才能得到结果呢?那么你需要使用一个事件循环。
事件循环是实现nodejs非阻塞I/O功能的关键基础。非阻塞I/O和事件循环是libuv(一个C库)提供的功能。
代码演示:
const eventloop={
队列:[],
loop() {
while(this.queue.length) {
const回调=this.queue.shift()
回调()
}
setTimeout(this.loop.bind(this),50)
},
添加(回调){
this.queue.push(回调)
}
}
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={
队列:[],
fsQueue: [],
定时器队列:[],
loop() {
while(this.queue.length) {
const回调=this.queue.shift()
回调()
}
this . fs queue . foreach(callback={
如果(完成){
回调()
}
})
setTimeout(this.loop.bind(this),50)
},
添加(回调){
this.queue.push(回调)
}
}
总结
首先我们弄清楚了什么是非阻塞I/O,就是当遇到I/O时,我立即跳过后面任务的执行,不会等待I/O的结果,在处理I/O时,我们会调用我们注册的事件处理函数,这就是所谓的事件驱动。异步编程是nodejs中最重要的环节,从回调函数到承诺,最后到async/await(异步逻辑是用同步方法写的)。
更多关于node的信息,请访问:nodejs教程!以上就是总结和分享nodejs的几个关键节点的细节。请多关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。