一文秒懂nodejs中的异步编程


Posted in NodeJs onJanuary 28, 2021

文章目录 简介同步异步和阻塞非阻塞javascript中的回调回调函数的错误处理回调地狱 ES6中的Promise什么是PromisePromise的特点Promise的优点Promise的缺点Promise的用法Promise的执行顺序 async和awaitasync的执行顺序async的特点 总结

简介

因为javascript默认情况下是单线程的,这意味着代码不能创建新的线程来并行执行。但是对于最开始在浏览器中运行的javascript来说,单线程的同步执行环境显然无法满足页面点击,鼠标移动这些响应用户的功能。于是浏览器实现了一组API,可以让javascript以回调的方式来异步响应页面的请求事件。

更进一步,nodejs引入了非阻塞的 I/O ,从而将异步的概念扩展到了文件访问、网络调用等。

今天,我们将会深入的探讨一下各种异步编程的优缺点和发展趋势。

同步异步和阻塞非阻塞

在讨论nodejs的异步编程之前,让我们来讨论一个比较容易混淆的概念,那就是同步,异步,阻塞和非阻塞。

所谓阻塞和非阻塞是指进程或者线程在进行操作或者数据读写的时候,是否需要等待,在等待的过程中能否进行其他的操作。

如果需要等待,并且等待过程中线程或进程无法进行其他操作,只能傻傻的等待,那么我们就说这个操作是阻塞的。

反之,如果进程或者线程在进行操作或者数据读写的过程中,还可以进行其他的操作,那么我们就说这个操作是非阻塞的。

同步和异步,是指访问数据的方式,同步是指需要主动读取数据,这个读取过程可能是阻塞或者是非阻塞的。而异步是指并不需要主动去读取数据,是被动的通知。

很明显,javascript中的回调是一个被动的通知,我们可以称之为异步调用。

javascript中的回调

javascript中的回调是异步编程的一个非常典型的例子:

document.getElementById('button').addEventListener('click', () => {
 console.log('button clicked!');
})

上面的代码中,我们为button添加了一个click事件监听器,如果监听到了click事件,则会出发回调函数,输出相应的信息。

回调函数就是一个普通的函数,只不过它被作为参数传递给了addEventListener,并且只有事件触发的时候才会被调用。

上篇文章我们讲到的setTimeout和setInterval实际上都是异步的回调函数。

回调函数的错误处理

在nodejs中怎么处理回调的错误信息呢?nodejs采用了一个非常巧妙的办法,在nodejs中,任何回调函数中的第一个参数为错误对象,我们可以通过判断这个错误对象的存在与否,来进行相应的错误处理。

fs.readFile('/文件.json', (err, data) => {
 if (err !== null) {
 //处理错误
 console.log(err)
 return
 }

 //没有错误,则处理数据。
 console.log(data)
})

回调地狱

javascript的回调虽然非常的优秀,它有效的解决了同步处理的问题。但是遗憾的是,如果我们需要依赖回调函数的返回值来进行下一步的操作的时候,就会陷入这个回调地狱。

叫回调地狱有点夸张了,但是也是从一方面反映了回调函数所存在的问题。

fs.readFile('/a.json', (err, data) => {
 if (err !== null) {
 fs.readFile('/b.json',(err,data) =>{
  //callback inside callback
 })
 }
})

怎么解决呢?

别怕ES6引入了Promise,ES2017引入了Async/Await都可以解决这个问题。

ES6中的Promise

什么是Promise

Promise 是异步编程的一种解决方案,比传统的解决方案“回调函数和事件”更合理和更强大。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

Promise的特点

Promise有两个特点:

对象的状态不受外界影响。

Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

一旦状态改变,就不会再变,任何时候都可以得到这个结果。

Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。

这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

Promise的优点

Promise将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise的缺点

  1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  2. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  3. 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

Promise的用法

Promise对象是一个构造函数,用来生成Promise实例:

var promise = new Promise(function(resolve, reject) { 
// ... some code 
if (/* 异步操作成功 */){ 
resolve(value); 
} else { reject(error); } 
}
);

promise可以接then操作,then操作可以接两个function参数,第一个function的参数就是构建Promise的时候resolve的value,第二个function的参数就是构建Promise的reject的error。

promise.then(function(value) { 
// success 
}, function(error) { 
// failure }
);

我们看一个具体的例子:

function timeout(ms){
 return new Promise(((resolve, reject) => {
  setTimeout(resolve,ms,'done');
 }))
}

timeout(100).then(value => console.log(value));

Promise中调用了一个setTimeout方法,并会定时触发resolve方法,并传入参数done。

最后程序输出done。

Promise的执行顺序

Promise一经创建就会立马执行。但是Promise.then中的方法,则会等到一个调用周期过后再次调用,我们看下面的例子:

let promise = new Promise(((resolve, reject) => {
 console.log('Step1');
 resolve();
}));

promise.then(() => {
 console.log('Step3');
});

console.log('Step2');

输出:
Step1
Step2
Step3

async和await

Promise当然很好,我们将回调地狱转换成了链式调用。我们用then来将多个Promise连接起来,前一个promise resolve的结果是下一个promise中then的参数。

链式调用有什么缺点呢?

比如我们从一个promise中,resolve了一个值,我们需要根据这个值来进行一些业务逻辑的处理。

假如这个业务逻辑很长,我们就需要在下一个then中写很长的业务逻辑代码。这样让我们的代码看起来非常的冗余。

那么有没有什么办法可以直接返回promise中resolve的结果呢?

答案就是await。

当promise前面加上await的时候,调用的代码就会停止直到 promise 被解决或被拒绝。

注意await一定要放在async函数中,我们来看一个async和await的例子:

const logAsync = () => {
 return new Promise(resolve => {
 setTimeout(() => resolve('小马哥'), 5000)
 })
}

上面我们定义了一个logAsync函数,该函数返回一个Promise,因为该Promise内部使用了setTimeout来resolve,所以我们可以将其看成是异步的。

要是使用await得到resolve的值,我们需要将其放在一个async的函数中:

const doSomething = async () => {
 const resolveValue = await logAsync();
 console.log(resolveValue);
}

async的执行顺序

await实际上是去等待promise的resolve结果我们把上面的例子结合起来:

const logAsync = () => {
 return new Promise(resolve => {
  setTimeout(() => resolve('小马哥'), 1000)
 })
}

const doSomething = async () => {
 const resolveValue = await logAsync();
 console.log(resolveValue);
}

console.log('before')
doSomething();
console.log('after')

上面的例子输出:

before
after
小马哥

可以看到,aysnc是异步执行的,并且它的顺序是在当前这个周期之后。

async的特点

async会让所有后面接的函数都变成Promise,即使后面的函数没有显示的返回Promise。

const asyncReturn = async () => {
 return 'async return'
}

asyncReturn().then(console.log)

因为只有Promise才能在后面接then,我们可以看出async将一个普通的函数封装成了一个Promise:

const asyncReturn = async () => {
 return Promise.resolve('async return')
}

asyncReturn().then(console.log)

总结

promise避免了回调地狱,它将callback inside callback改写成了then的链式调用形式。

但是链式调用并不方便阅读和调试。于是出现了async和await。

async和await将链式调用改成了类似程序顺序执行的语法,从而更加方便理解和调试。

我们来看一个对比,先看下使用Promise的情况:

const getUserInfo = () => {
 return fetch('/users.json') // 获取用户列表
 .then(response => response.json()) // 解析 JSON
 .then(users => users[0]) // 选择第一个用户
 .then(user => fetch(`/users/${user.name}`)) // 获取用户数据
 .then(userResponse => userResponse.json()) // 解析 JSON
}

getUserInfo()

将其改写成async和await:

const getUserInfo = async () => {
 const response = await fetch('/users.json') // 获取用户列表
 const users = await response.json() // 解析 JSON
 const user = users[0] // 选择第一个用户
 const userResponse = await fetch(`/users/${user.name}`) // 获取用户数据
 const userData = await userResponse.json() // 解析 JSON
 return userData
}

getUserInfo()

可以看到业务逻辑变得更加清晰。同时,我们获取到了很多中间值,这样也方便我们进行调试。

到此这篇关于深入理解nodejs中的异步编程的文章就介绍到这了,更多相关nodejs异步编程内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

NodeJs 相关文章推荐
PHPStorm 2020.1 调试 Nodejs的多种方法详解
Sep 17 NodeJs
Nodejs实现多人同时在线移动鼠标的小游戏分享
Dec 06 NodeJs
nodejs中的fiber(纤程)库详解
Mar 24 NodeJs
深入nodejs中流(stream)的理解
Mar 27 NodeJs
nodejs个人博客开发第七步 后台登陆
Apr 12 NodeJs
详解nodejs微信jssdk后端接口
May 25 NodeJs
理解nodejs的stream和pipe机制的原理和实现
Aug 12 NodeJs
NodeJS收发GET和POST请求的示例代码
Aug 25 NodeJs
原生nodejs使用websocket代码分享
Apr 07 NodeJs
nodejs取得当前执行路径的方法
May 13 NodeJs
通过nodejs 服务器读取HTML文件渲染到页面的方法
May 17 NodeJs
在nodejs中创建child process的方法
Jan 26 #NodeJs
nodejs中使用worker_threads来创建新的线程的方法
Jan 22 #NodeJs
Nodejs 微信小程序消息推送的实现
Jan 20 #NodeJs
Nodejs实现微信分账的示例代码
Jan 19 #NodeJs
nodejs中的异步编程知识点详解
Jan 17 #NodeJs
nodejs+express最简易的连接数据库的方法
Dec 23 #NodeJs
windows如何把已安装的nodejs高版本降级为低版本(图文教程)
Dec 14 #NodeJs
You might like
第4章 数据处理-php正则表达式-郑阿奇(续)
2011/07/04 PHP
php实现的Captcha验证码类实例
2014/09/22 PHP
PHP中curl_setopt函数用法实例分析
2015/04/16 PHP
php+ajax实现带进度条的上传图片功能【附demo源码下载】
2016/09/14 PHP
PHP实现自动发送邮件功能代码(qq 邮箱)
2017/08/18 PHP
根据出生日期自动取得星座的js代码
2010/07/20 Javascript
基于jquery插件实现常见的幻灯片效果
2013/11/01 Javascript
图文详解Heap Sort堆排序算法及JavaScript的代码实现
2016/05/04 Javascript
js检测离开或刷新页面时表单数据是否更改的方法
2016/08/02 Javascript
微信小程序教程之本地图片上传(leancloud)实例详解
2016/11/16 Javascript
IntersectionObserver API 详解篇
2016/12/11 Javascript
jqgrid实现简单的单行编辑功能
2017/09/30 Javascript
深入探讨JavaScript的最基本部分之执行上下文
2019/02/12 Javascript
vue项目移动端实现ip输入框问题
2019/03/19 Javascript
详解在Angular4中使用ng2-baidu-map的方法
2019/06/19 Javascript
vue实现自定义H5视频播放器的方法步骤
2019/07/01 Javascript
微信小程序 扭蛋抽奖机css3动画实现详解
2019/07/19 Javascript
Vue 封装防刷新考试倒计时组件的实现
2020/06/05 Javascript
python使用Flask框架获取用户IP地址的方法
2015/03/21 Python
Python中对元组和列表按条件进行排序的方法示例
2015/11/10 Python
python 字典(dict)按键和值排序
2016/06/28 Python
python读取文本中的坐标方法
2018/10/14 Python
Win10系统下安装labelme及json文件批量转化方法
2019/07/30 Python
python输出决策树图形的例子
2019/08/09 Python
Python 图像对比度增强的几种方法(小结)
2019/09/25 Python
Tensorflow 模型转换 .pb convert to .lite实例
2020/02/12 Python
Python astype(np.float)函数使用方法解析
2020/06/08 Python
Python实现寻找回文数字过程解析
2020/06/09 Python
如何解决python多种版本冲突问题
2020/10/13 Python
Kivari官网:在线购买波西米亚服装
2018/10/29 全球购物
惠普新加坡官方商店:HP Singapore
2020/04/17 全球购物
开水果连锁店创业计划书
2013/12/29 职场文书
经典演讲稿汇总
2014/05/19 职场文书
创业计划书之书店
2019/09/10 职场文书
MySql子查询IN的执行和优化的实现
2021/08/02 MySQL
Nginx虚拟主机的搭建的实现步骤
2022/01/18 Servers