干货 | Nodejs异步编程详解 - Go语言中文社区

干货 | Nodejs异步编程详解


点击上方“中兴开发者社区”,关注我们

每天读一篇一线开发者原创好文

640?wx_fmt=png

作者简介

作者廖元之是一名优秀的全栈开发工程师,对前端性能优化,数据可视化等有自己独到的理解。今天他为我们带来nodejs异步编程解决方案,希望对无论是web开发还是仅仅使用Nodejs做脚本的同学都有所帮助。


写在前面

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

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


Nodejs的异步

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

先看几行python代码

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

  2. print(file_obj.read())

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

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

 
  1. const fs = require('fs')

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

  3.    if(err){

  4.        // throw an err

  5.    }else{

  6.        console.log(doc)

  7.    }

  8. )

看起来相当的麻烦。

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

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

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

 
  1. //callbackHell.js

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

  3.    //do something

  4.    let input = someFunc(doc)

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

  6.        //do something

  7.        let input2 = someFunc2(doc2)

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

  9.            // err capture

  10.            // some other async operations

  11.        })

  12.    })

  13. })

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


Promise

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

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

废话少说,直接上代码:

 
  1. function promisifyAsyncFunc(){

  2.    return new Promise((resolve,reject)=>{

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

  4.            if(err)reject(err)

  5.            else resolve(doc)

  6.        })

  7.    })

  8. }

  9. promisifyAsyncFunc()

  10.    .then(res=>{

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

  12.    })

  13.    .catch(rej=>{

  14.        console.log()

  15. })

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

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

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

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

 
  1. //promisifY.js

  2. function promisifyAsyncFunc(){

  3.    return new Promise((resolve,reject)=>{

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

  5.            if(err)reject(err)

  6.            else resolve(doc)

  7.        })

  8.    })

  9. }

  10. function promisifyAsyncFunc2(input){

  11.    return new Promise((resolve,reject)=>{

  12.        let output1 = someFunc(input)

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

  14.            if(err)reject(err)

  15.            else resolve({

  16.                output1,

  17.                doc

  18.            })

  19.        })

  20.    })

  21. }

  22. function promisifyAsyncFunc3({output1,doc}){

  23.    return new Promise((resolve,reject)=>{

  24.        let outpu2 = someFunc2(doc)

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

  26.                    // err capture

  27.        })

  28.    })

  29. }

  30. // some other prmisify function

  31. promisifyAsyncFunc()

  32. .then(promisifyAsyncFunc2)

  33. .then(promisifyAsyncFunc3)

  34. //.then()

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

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


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


Generator

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


async/await

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

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

 
  1. async function main(){

  2.    const ret = await someAsynFunc();

  3.    const ret2 = await otherAsyncFunc(ret)

  4.    return someSyncFunc(ret,ret2)

  5. }

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

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

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

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

 
  1. //async/await.js

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

  3. const read = promisify(fs.read)

  4. const write = promisify(fs.write)

  5. async function callAsyncSync(){

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

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

  8.     版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
    原文链接:https://blog.csdn.net/O4dC8OjO7ZL6/article/details/79922999
    站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

  • 发表于 2021-04-10 16:58:59
  • 阅读 ( 415 )
  • 分类:前端

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢