深入理解nodejs中的异步编程 nodejs异步编程及原理
qiyuwang 2024-10-31 15:50 20 浏览 0 评论
简介
因为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的缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于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()
可以看到业务逻辑变得更加清晰。同时,我们获取到了很多中间值,这样也方便我们进行调试。
本文作者:flydean程序那些事
本文链接:http://www.flydean.com/nodejs-async/
本文来源:flydean的博客
欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
相关推荐
- 基于Docker方式安装与部署Camunda流程引擎
-
1Camunda简介官网:https://docs.camunda.org/manual/7.19/installation/docker/Camunda是一个轻量级、开源且高度灵活的工作流和决策自...
- 宝塔Linux面板如何部署Java项目?(宝塔面板 linux)
-
通过宝塔面板部署Java还是很方便的,至少不需要自己输入tomcat之类的安装命令了。在部署java项目前,我还是先说下目前的系统环境,如果和我的系统环境不一样,导致部署不成功,那你可能需要去找其他资...
- 浪潮服务器如何用IPMI安装Linux系统
-
【注意事项】此处以浪潮服务器为例进行演示所需使用的软件:Chrome浏览器个人PC中需要预先安装java,推荐使用jdk-8u181-windows-x64.exe【操作步骤】1、在服务器的BIOS中...
- Centos7环境Hadoop3集群搭建(hadoop集群环境搭建实验报告)
-
由于项目需要存储历史业务数据,经过评估数据量会达到100亿以上,在原有mongodb集群和ES集群基础上,需要搭建Hbase集群进行调研,所以首先总结一下Hadoop集群的搭建过程。一、三个节点的集群...
- Hadoop高可用集群搭建及API调用(hadoop高可用原理)
-
NameNodeHA背景在Hadoop1中NameNode存在一个单点故障问题,如果NameNode所在的机器发生故障,整个集群就将不可用(Hadoop1中虽然有个SecorndaryNameNo...
- 使用Wordpress搭建一个属于自己的网站
-
现在开源的博客很多,但是考虑到wordpress对网站的seo做的很好,插件也多。并且全世界流量排名前1000万的网站有33.4%是用Wordpress搭建的!所以尝试用Wordpress搭建一个网站...
- Centos 安装 Jenkins(centos 安装ssh)
-
1、Java安装查看系统是否已安装Javayumlistinstalled|grepjava...
- Java教程:gitlab-使用入门(java中的git)
-
1导读本教程主要讲解了GitLab在项目的环境搭建和基本的使用,可以帮助大家在企业中能够自主搭建GitLab服务,并且可以GitLab中的组、权限、项目自主操作...
- Dockerfile部署Java项目(docker部署java应用)
-
1、概述本文主要会简单介绍什么是Docker,什么是Dockerfile,如何安装Docker,Dockerfile如何编写,如何通过Dockerfile安装jar包并外置yaml文件以及如何通过do...
- 如何在Eclipse中搭建Zabbix源码的调试和开发环境
-
Zabbix是一款非常优秀的企业级软件,被设计用于对数万台服务器、虚拟机和网络设备的数百万个监控项进行实时监控。Zabbix是开放源码和免费的,这就意味着当出现bug时,我们可以很方便地通过调试源码来...
- Java路径-02-Java环境配置(java环境搭建及配置教程)
-
1Window环境配置1.1下载...
- 35.Centos中安装python和web.py框架
-
文章目录前言1.Centos7python:2.Centos8python:3.进行下载web.py框架然后应用:4.安装好之后进行验证:5.总结:前言...
- 《我的世界》服务器搭建(我的世界服务器如何搭建)
-
1.CentOS7环境1.1更改YUM源#下载YUM源文件curl-o/etc/yum.repos.d/CentOS-Base.repohttps://mirrors.aliyun.com...
- CentOS 7 升级 GCC 版本(centos7.4升级7.5)
-
1.GCC工具介绍GCC编译器:...
- Linux安装Nginx详细教程(linux安装配置nginx)
-
环境准备1.因为Nginx依赖于gcc的编译环境,所以,需要安装编译环境来使Nginx能够编译起来。命令:yuminstallgcc-c++显示完毕,表示安装完成:2.Nginx的http模块需要...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 基于Docker方式安装与部署Camunda流程引擎
- 宝塔Linux面板如何部署Java项目?(宝塔面板 linux)
- 浪潮服务器如何用IPMI安装Linux系统
- Centos7环境Hadoop3集群搭建(hadoop集群环境搭建实验报告)
- Hadoop高可用集群搭建及API调用(hadoop高可用原理)
- 使用Wordpress搭建一个属于自己的网站
- Centos 安装 Jenkins(centos 安装ssh)
- Java教程:gitlab-使用入门(java中的git)
- Dockerfile部署Java项目(docker部署java应用)
- 如何在Eclipse中搭建Zabbix源码的调试和开发环境
- 标签列表
-
- navicat无法连接mysql服务器 (65)
- 下横线怎么打 (71)
- flash插件怎么安装 (60)
- lol体验服怎么进 (66)
- ae插件怎么安装 (62)
- yum卸载 (75)
- .key文件 (63)
- cad一打开就致命错误是怎么回事 (61)
- rpm文件怎么安装 (66)
- linux取消挂载 (81)
- ie代理配置错误 (61)
- ajax error (67)
- centos7 重启网络 (67)
- centos6下载 (58)
- mysql 外网访问权限 (69)
- centos查看内核版本 (61)
- ps错误16 (66)
- nodejs读取json文件 (64)
- centos7 1810 (59)
- 加载com加载项时运行错误 (67)
- php打乱数组顺序 (68)
- cad安装失败怎么解决 (58)
- 因文件头错误而不能打开怎么解决 (68)
- js判断字符串为空 (62)
- centos查看端口 (64)