nodejs中的异步编程知识点详解


Posted in NodeJs onJanuary 17, 2021

简介

因为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有两个特点:

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

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

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

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

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

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

Promise的优点

  1. Promise将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
  2. 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 相关文章推荐
nodejs读取memcache示例分享
Jan 02 NodeJs
nodeJS代码实现计算交社保是否合适
Mar 09 NodeJs
Nodejs从有门道无门菜鸟起飞必看教程
Jul 20 NodeJs
NodeJS测试框架mocha入门教程
Mar 28 NodeJs
nodejs判断文件、文件夹是否存在及删除的方法
Nov 10 NodeJs
NodeJS爬虫实例之糗事百科
Dec 14 NodeJs
nodejs搭建本地服务器轻松解决跨域问题
Mar 21 NodeJs
nodeJS模块简单用法示例
Apr 21 NodeJs
Nodejs实现的操作MongoDB数据库功能完整示例
Feb 02 NodeJs
5分钟教你用nodeJS手写一个mock数据服务器的方法
Sep 10 NodeJs
Nodejs监控事件循环异常示例详解
Sep 22 NodeJs
详解nodejs内置模块
May 06 NodeJs
nodejs+express最简易的连接数据库的方法
Dec 23 #NodeJs
windows如何把已安装的nodejs高版本降级为低版本(图文教程)
Dec 14 #NodeJs
NodeJS配置CORS实现过程详解
Dec 02 #NodeJs
如何利用nodejs自动定时发送邮件提醒(超实用)
Dec 01 #NodeJs
nodeJs项目在阿里云的简单部署
Nov 27 #NodeJs
如何利用nodejs实现命令行游戏
Nov 24 #NodeJs
NodeJS模块Buffer原理及使用方法解析
Nov 11 #NodeJs
You might like
php地址引用(php地址引用的效率问题)
2012/03/23 PHP
LINUX下PHP程序实现WORD文件转化为PDF文件的方法
2016/05/13 PHP
PHP实现正则表达式分组捕获操作示例
2018/02/03 PHP
PHP设计模式之模板模式定义与用法详解
2018/12/20 PHP
Laravel框架FormRequest中重写错误处理的方法
2019/02/18 PHP
Laravel 微信小程序后端实现用户登录的示例代码
2019/11/26 PHP
如何在PHP中使用AES加密算法加密数据
2020/06/24 PHP
javascript 当前日期转化为中文的实现代码
2010/05/13 Javascript
JavaScript关闭当前页面(窗口)不带任何提示
2014/03/26 Javascript
JavaScript使用HTML5的window.postMessage实现跨域通信例子
2014/04/11 Javascript
js检测浏览器版本、核心、是否移动端示例
2014/04/24 Javascript
js图片自动轮播代码分享(js图片轮播)
2014/05/06 Javascript
JavaScript实现窗口抖动效果
2016/10/19 Javascript
jQuery+json实现动态创建复杂表格table的方法
2016/10/25 Javascript
IE8利用自带的setCapture和releaseCapture解决iframe的拖拽事件方法
2016/10/25 Javascript
理解javascript中的闭包
2017/01/11 Javascript
利用node.js写一个爬取知乎妹纸图的小爬虫
2017/05/03 Javascript
基于React+Redux的SSR实现方法
2018/07/03 Javascript
使用js实现一个简单的滚动条过程解析
2019/09/10 Javascript
JS数组及对象遍历方法代码汇总
2020/06/16 Javascript
ant design vue中表格指定格式渲染方式
2020/10/28 Javascript
[01:45]IMBATV TI4前线报道-选手到达
2014/07/07 DOTA
Python的Flask框架与数据库连接的教程
2015/04/20 Python
Python yield与实现方法代码分析
2018/02/06 Python
python实现祝福弹窗效果
2019/04/07 Python
HTML5 Canvas实现玫瑰曲线和心形图案的代码实例
2014/04/10 HTML / CSS
网络艺术零售业的先驱者:artrepublic
2017/09/26 全球购物
ASOS比利时:英国线上零售商及自有品牌
2018/07/29 全球购物
一年级学生评语
2014/04/23 职场文书
党员批评与自我批评思想汇报(集锦)
2014/09/14 职场文书
单位个人查摆问题及整改措施
2014/10/28 职场文书
捐款感谢信
2015/01/20 职场文书
思想道德自我评价2015
2015/03/09 职场文书
创业计划之特色精品店
2019/08/12 职场文书
python opencv通过4坐标剪裁图片
2021/06/05 Python
gojs实现蚂蚁线动画效果
2022/02/18 Javascript