0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

关于Nodejs中最关键也是最难的异步编程做一些介绍和讲解

8nfr_ZTEdevelop 来源:未知 作者:李倩 2018-04-13 10:17 次阅读

写在前面

python语法简单易上手,又有大量的库的支持,在工具脚本这格领域体现了它的价值,地位不可动摇。我本来是也是使用python编写一些脚本的,但由于一些巧合、契机,我接触到了Nodejs,基于一些个人原因,我更倾向使用Nodejs来编写平时使用的工具脚本,包括数据采集入库之类,但由于js这个语言本身的一些特性,使得Nodejs作为脚本来开发的话难度曲线相对陡峭,这篇文章我就关于Nodejs中最关键也是最难的异步编程做一些介绍和讲解

$这篇文章面向的读者绝对不是对Nodejs完全没用过的同学,读者需要对Nodejs有简单的了解$

Nodejs的异步

Nodejs本身是单线程的,底层核心库是Google开发的V8引擎,而主要负责实现Nodejs的异步功能的是一个叫做libuv的开源库,github上可以找到它。

先看几行python代码

file_obj = open('./test.txt')

print(file_obj.read())

这行代码逻辑相当简单,打印根目录下一个名为test的txt文件内容

相同的操作用Nodejs写是这样:

const fs = require('fs')

fs.read('./test.txt',(err,doc)=>{

if(err){

// throw an err

}else{

console.log(doc)

}

)

看起来相当的麻烦。

为什么要这样写?根本原因就是Node的特点,异步机制。关于异步机制的深入理解我可能会另写一篇文章

fs.read()本身是一个异步函数,所以返回值是异步的,必须在回调函数中捕获,所以得写成这个样子。

一两个异步操作可能还好,但如果相当多的异步操作需要串行执行,就会出现以下这种代码:

//callbackHell.js

fs.read('./test1.txt',(err,doc)=>{

//do something

let input = someFunc(doc)

fs.read('./test2.txt',(err,doc2)=>{

//do something

let input2 = someFunc2(doc2)

fs.write('./output.txt',input+input2,(err)=>{

// err capture

// some other async operations

})

})

})

连续的回调函数的嵌套,会使得代码变得冗长,易读性大幅度降低并且难以维护,这种情况也被称作回调地狱(calllback hell),为了解决这个问题,ES标准推出了一套异步编程解决方案

Promise

人们对于新事物的快速理解一般基于此新事物与生活中某种事物或者规律的的相似性,但这个promise并没有这种特点,在我看来,可以去类比promise这个概念的东西相当少,而且类比得相当勉强,但这也并不意味着promise难以理解。

promise本身是一个对象,但是可以看做是是一种工具,一种从未见过的工具,解决的是Nodejs异步接口串行执行导致的回调地狱问题,它本身对代码逻辑没有影响

废话少说,直接上代码:

function promisifyAsyncFunc(){

returnnewPromise((resolve,reject)=>{

fs.read('./test1.txt'.(err.doc)=>{

if(err)reject(err)

else resolve(doc)

})

})

}

promisifyAsyncFunc()

.then(res=>{

console.log(`read file success ${res}`)

})

.catch(rej=>{

console.log()

})

与之前的异步代码不同的是,我们在异步函数外层包了一层返回promise对象的函数,promise对象向自己包裹的函数里传入了两个函数参数resolve和reject,在异步函数内部通过调用resolve向外传递操作成功数据,调用reject向外传递错误信息

关于promise对象使用的语法牵涉到es6的最新规范和函数式编程,这里不做详细介绍

接着我们调用这个函数,链式调用promise对象提供的接口函数.then(function(res){//TODO})将异步函数向外传递的值取出来,使用.catch()捕获内部传递的错误。

最基本的promise用法大致就是这样,但这样看依然没明白它如何避免了回调地狱,这里我们使用promise改写callbackHell.js文件

//promisifY.js

function promisifyAsyncFunc(){

returnnewPromise((resolve,reject)=>{

fs.read('./test1.txt'.(err.doc)=>{

if(err)reject(err)

else resolve(doc)

})

})

}

function promisifyAsyncFunc2(input){

returnnewPromise((resolve,reject)=>{

let output1 = someFunc(input)

fs.read('./test2.txt'.(err.doc)=>{

if(err)reject(err)

else resolve({

output1,

doc

})

})

})

}

function promisifyAsyncFunc3({output1,doc}){

returnnewPromise((resolve,reject)=>{

let outpu2 = someFunc2(doc)

fs.write('./output.txt',output1+output2,(err)=>{

// err capture

})

})

}

// some other prmisify function

promisifyAsyncFunc()

.then(promisifyAsyncFunc2)

.then(promisifyAsyncFunc3)

//.then()

代码这样写应该会看的比较清楚,我们把每个异步函数都封装在promise对象里面,然后通过promise的链式调用来传递数据,从而避免了回调地狱。

这样的代码可读性和维护性要好上不少,但很显然代码量增加了一些,就是每个函数的封装过程,但node里的util库中的promisify函数提供了将满足node回调规则的函数自动转换为promise对象的功能,若没有对异步操作的复杂订制,可以使用这个函数减少代码量

虽然promise相对于原生的回调来说已经是相当良好的编程体验,但对于追求完美的程序员来说,这依旧不够优美,于是es规范在演进的过程中又推出了新的异步编程方式

Generator

Generator并不是最终的异步解决方案,而是Promise向最终方案演进的中间产物,但是其中利用到的迭代器设计模式值得我们学习和参考。这里不对这种方法多做介绍,因为有了async,一般就不再使用Generator了。

async/await

Async/Await其实是Generator的语法糖,但是因为使用的时候使异步编程似乎完全变成了同步编程,体验异常的好,而且这是官方推出的最新规范,所以广受推崇,这里就对如何使用它进行一些介绍说明。

先看Async的语法,用起来真的是相当简单

async function main(){

const ret = await someAsynFunc();

const ret2 = await otherAsyncFunc(ret)

return someSyncFunc(ret,ret2)

}

定义一个函数,函数申明前加上一个async关键字,说明这个函数内部有需要同步执行的异步函数

此函数需要同步执行的异步函数必须返回的是promise对象,就是我们之前用promise包裹的那个形式

在需同步执行的异步函数调用表达式前加上await关键字,这时函数会同步执行并将promise对象resolve出来的数据传递给等号之前的变量

我们再使用async/await改写promisify.js文件

//async/await.js

const promisify = require('util').promisify //引入promisify函数,简化promise代码

const read = promisify(fs.read)

const write = promisify(fs.write)

async function callAsyncSync(){

const res1 = await read('./test1.txt')

const res2 = await read('./test2.txt')

const output1 = someFunc(res1)

const output2 = someFunc(res2)

write('./output.txt',output1+output2)

return

}

这样看代码就像是同步的, 比python速度还快很多,可惜的就是相对于python学习曲线比较陡峭。

这种方式写出的代码可读性可维护性可以说是非常强,完全没有之前的回调地狱或者原生promise带来的副作用。

进阶

试想这么一种场景:

我们需要从多个数据库中读取数据,读取完成的顺序无所谓.

我们需要在多次数据读取全部完成之后再从每个数据中筛选出某种相同的属性

再对这些属性进行一些自定义操作,得到结果数据

最后将结果数据插入某个数据库

假设每一步的具体实现函数全部已经编写完成,所有异步的函数都已经封装成promise,那么用原生js组装以上四步代码需要怎么写?

我粗略估计一下可能需要二十行左右,而且代码的可读性不会很好,这里我简单介绍一个库:RxJS,中文名为响应式JS。

响应式编程发展已久,许多语言都有实现相应的库,对应的名字也以Rx开头,比如RxJava

不说RxJS的设计原理,它的使用都牵涉到多种设计模式和技术,包括观察者模式,管道模式,函数式编程等,可以说这是一个上手难度相当大的技术,但它带来的编程体验是相当的好,这里我给出使用RxJS实现以上四步的代码

constOb= require('rxjs/Rx').Observerble //Rxjs的核心观察者对象

const promiseArray = require('./readDatabase')//封装好的读数据库函数数组

const myfilter = require('./filter')//数据属性过滤函数

const operation = require('./operation')//自定义的逻辑操作

const insert = require('./insert')//数据库插入函数

Ob.forkJoin(...promiseArray.map(v=>Ob.fromPromise(v)))

.filter(myfilter)

.switchMap(operations)

.subscribe(insert)

除去将自定义的函数引入,四步操作每步只对应一行代码,这样写真的是非常简洁。

我们平时常用的,甚至是不常用的对数据的操作,基本都能在RxJS官网里都能找到封装好的API,有兴趣的同学可以多关注这个库,就我自己平时开发时的体验来看,这个库是相当的好用,但是要有一定的心理准备,因为确实有一些难度。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编程
    +关注

    关注

    88

    文章

    3621

    浏览量

    93785
  • python
    +关注

    关注

    56

    文章

    4797

    浏览量

    84775
  • nodejs
    +关注

    关注

    0

    文章

    20

    浏览量

    4238

原文标题:干货 | Nodejs异步编程详解

文章出处:【微信号:ZTEdeveloper,微信公众号:中兴开发者社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    关于编程一些问题

    开始学习编程的时候没想那么多,可是现在编的程序大了之后开始考虑一些算法的东西 因为编程的时候都是在大脑里想的逻辑顺序 然后就直接编程 可是随着程序代码量大了之后,自己越来越困难了
    发表于 10-30 20:59

    关于AVR的一些编程问题,请教请教

    关于AVR的一些编程问题,请教请教
    发表于 05-19 09:39

    讲解Matlab的一些编程语句

    第5章 Matlab简易使用之常用编程语句本期教程主要是讲解Matlab的一些编程语句。目录第5章 Matlab简易使用之常用编程语句5.1
    发表于 08-17 07:45

    有线IAP用户程序升级的一些心得分享

    如果你没有时间,想吃“快餐”,请直接移步至文末。在上篇博文中,我介绍了我有线IAP用户程序升级的一些心得,有线升级并不是我的目的,无线才是,所以就有了这篇文章。这篇文章介绍
    发表于 02-16 06:33

    关于stm32的一些简单的介绍

    #序言本文章是关于stm的一些简单的介绍,全部都是个人学习的一些经验总结,分享给想要自学stm32的朋友们用于入门。其中部分内容借鉴于《stm32中文参考手册》和《cortex-m3权
    发表于 02-24 06:30

    设计开关电源的一些关键问题

    对于开关电源的噪声,除了芯片本身,Layout的设计最为重要,记录一些相关的技巧。不少关于EMI的观念具有通用性。下面我们谈谈关于开关电源设计的一些
    发表于 12-11 17:09 3173次阅读

    LTE的其它一些关键技术介绍

    以前寫論文收集的一些資料,學習射頻電路、无线通信技术的好資料!!!尤其是关于4G/LTE方面的学习!!!
    发表于 06-27 15:11 0次下载

    关于PID一些常用知识

    本文档详细介绍分析了关于PID的一些常用知识
    发表于 08-29 14:22 2次下载

    关于STM32f1和f4编程一些问题解决方案

    关于STM32f1和f4编程一些问题
    发表于 04-03 15:31 5次下载

    树莓派利用Nodejs+Express开发关于硬件监控的服务讨论

    当前Nodejs+Express在web应用开发领域很是红火,但是否能用其开发关于硬件的服务?回答当然是肯定的,本文就将简要介绍相关一些开发要点。
    发表于 04-22 15:15 1518次阅读

    关于C语言的一些特殊功能介绍

    C语言之所以那么受欢迎,除了C语言历史悠久之外,还有它具有一些编程语言没有的功能。那么,今年我们就来了解下C语言的一些特殊功能。
    的头像 发表于 08-18 15:31 2463次阅读
    <b class='flag-5'>关于</b>C语言的<b class='flag-5'>一些</b>特殊功能<b class='flag-5'>介绍</b>

    图像捕获是AI中最难的问题之

     Azure AI的CVP Eric Boyd在接受Engadget采访时说:“图像捕获是AI中最难的问题之。它不仅代表理解场景中的对象,还包括它们之间的交互方式,以及如何描述它们。
    的头像 发表于 10-15 14:03 1723次阅读

    电脑的一些硬件问题讲解

    本文档的主要内容详细介绍的是电脑的一些硬件问题讲解
    发表于 10-20 08:00 14次下载
    电脑的<b class='flag-5'>一些</b>硬件问题<b class='flag-5'>讲解</b>

    NEON编程中的一些常见优化技巧

      读过上篇文章“ARM NEON快速上手指南”之后,相信你已经对ARM NEON编程有了基本的认识。但在真正利用ARM NEON优化程序性能时,还有很多编程技巧和注意事项。本文将结合本人的
    的头像 发表于 12-12 09:11 2018次阅读

    nodejs 后端技术介绍

    笔者最开始学的后端技术是 python 的 Django 框架,由于很久没有使用过 python 语法,便想着了解一些 nodejs 的后端技术。下面将最近的收获总结下。
    的头像 发表于 05-05 16:41 1126次阅读