文章目录 简介同步异步和阻塞非阻塞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中的异步编程的更多相关文章

  1. 在使用内模块的时候需要先将所需的内置模块进行引入、OS模块在nodejs中OS模块提供了与操作系统相关的属性和方法/......

  2. three.js cannon.js物理引擎之Heightfield

    今天郭先生说一说cannon.js物理引擎之Heightfield高度场,学过场论的朋友都知道物理学中把某个物理量在......

  3. typescript编写微信小程序创建项目的方法

    创建项目在微信开发者工具创建项目,在语言中选择 TypeScript改造项目编辑 package.json 文件,修......

  4. Socket.IO基础教程

    什么是Socket.IOSocket.IO是一个库,可用于在浏览器和服务器之间进行实时,双向和基于事件的通信。它包括......

  5. 使用nodejs和express搭建http web服务

    目录简介使用nodejs搭建HTTP web服务请求nodejs服务第三方lib请求post获取http请求的正文E......

  6. nodejs事件和事件循环详解

    目录简介nodejs中的事件循环phase详解timerspending callbacksidle, prepar......

  7. Node使用koa2实现一个简单JWT鉴权的方法

    JWT 简介什么是 JWT全称 JSON Web Token , 是目前最流行的跨域认证解决方案。基本的实现是服务端......

  8. node.js常用内置模块一

    在使用内模块的时候需要先将所需的内置模块进行引入、OS模块在nodejs中OS模块提供了与操作系统相关的属性和方法/......

  9. 在nodejs中创建cluster

    目录简介cluster集群cluster详解cluster中的eventcluster中的方法cluster中的属性......

  10. JS实现公告上线滚动效果

    本文实例为大家分享了JS实现公告上线滚动效果的具体代码,供大家参考,具体内容如下实现的效果如下,新闻公告上下滚动。代......

随机推荐

  1. python 实现客户端与服务端的通信

    函数介绍Socket对象方法:服务端:函数描述.bind()绑定地址关键字,AF_INET下以元组的形式表示地址。常......

  2. python中lower函数实现方法及用法讲解

    之前小编介绍过python中将字符串小写字符转为大写的upper函数的使用方法(upper函数)。有将小写转为大写的......

  3. 如何用python写个模板引擎

    一.实现思路本文讲解如何使用python实现一个简单的模板引擎, 支持传入变量, 使用if判断和for循环语句, 最......

  4. 基于Python的接口自动化-读写excel文件

    引言使用python进行接口测试时常常需要接口用例测试数据、断言接口功能、验证接口响应状态等,如果大量的接口测试用例......

  5. Java中Jackson快速入门

    Java生态圈中有很多处理JSON和XML格式化的类库,Jackson是其中比较著名的一个。虽然JDK自带了XML处......

  6. Linux常用命令:文件操作命令

    Linux系统命令主要包括文件操作、网络命令和性能命令,本文介绍常用文件操作命令。修改文件属性文件类型:普通文件:-......

  7. 如何在 Vue 表单中处理图片

    问题:我在 Vue 中有一个 form 表单,用于上传博客帖子,它有标题、正文、描述、片段和图片等范围。所有的一切都......

  8. 超有用的linux笔记

    名词解释根目录说明tree -L 1.├── bin -> usr/bin # 英语binary的缩写,表示”......

  9. C#中File和FileStream的简单介绍和用法

    前言在近期的工作过程中发现自己的基础比较薄弱,所以最近在恶补基础知识。下面就对我所学习的File类和FileStre......

  10. JS addEventListener()和attachEvent()方法实现注册事件

    在 JavaScript 的 DOM 事件模型中,通过调用对象的 addEventListener() 方法注册事件......