0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

關(guān)于Nodejs中最關(guān)鍵也是最難的異步編程做一些介紹和講解

8nfr_ZTEdevelop ? 來源:未知 ? 作者:李倩 ? 2018-04-13 10:17 ? 次閱讀

寫在前面

python語法簡單易上手,又有大量的庫的支持,在工具腳本這格領(lǐng)域體現(xiàn)了它的價(jià)值,地位不可動(dòng)搖。我本來是也是使用python編寫一些腳本的,但由于一些巧合、契機(jī),我接觸到了Nodejs,基于一些個(gè)人原因,我更傾向使用Nodejs來編寫平時(shí)使用的工具腳本,包括數(shù)據(jù)采集入庫之類,但由于js這個(gè)語言本身的一些特性,使得Nodejs作為腳本來開發(fā)的話難度曲線相對陡峭,這篇文章我就關(guān)于Nodejs中最關(guān)鍵也是最難的異步編程做一些介紹和講解

$這篇文章面向的讀者絕對不是對Nodejs完全沒用過的同學(xué),讀者需要對Nodejs有簡單的了解$

Nodejs的異步

Nodejs本身是單線程的,底層核心庫是Google開發(fā)的V8引擎,而主要負(fù)責(zé)實(shí)現(xiàn)Nodejs的異步功能的是一個(gè)叫做libuv的開源庫,github上可以找到它。

先看幾行python代碼

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

print(file_obj.read())

這行代碼邏輯相當(dāng)簡單,打印根目錄下一個(gè)名為test的txt文件內(nèi)容

相同的操作用Nodejs寫是這樣:

const fs = require('fs')

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

if(err){

// throw an err

}else{

console.log(doc)

}

)

看起來相當(dāng)?shù)穆闊?/p>

為什么要這樣寫?根本原因就是Node的特點(diǎn),異步機(jī)制。關(guān)于異步機(jī)制的深入理解我可能會(huì)另寫一篇文章

fs.read()本身是一個(gè)異步函數(shù),所以返回值是異步的,必須在回調(diào)函數(shù)中捕獲,所以得寫成這個(gè)樣子。

一兩個(gè)異步操作可能還好,但如果相當(dāng)多的異步操作需要串行執(zhí)行,就會(huì)出現(xiàn)以下這種代碼:

//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

})

})

})

連續(xù)的回調(diào)函數(shù)的嵌套,會(huì)使得代碼變得冗長,易讀性大幅度降低并且難以維護(hù),這種情況也被稱作回調(diào)地獄(calllback hell),為了解決這個(gè)問題,ES標(biāo)準(zhǔn)推出了一套異步編程解決方案

Promise

人們對于新事物的快速理解一般基于此新事物與生活中某種事物或者規(guī)律的的相似性,但這個(gè)promise并沒有這種特點(diǎn),在我看來,可以去類比promise這個(gè)概念的東西相當(dāng)少,而且類比得相當(dāng)勉強(qiáng),但這也并不意味著promise難以理解。

promise本身是一個(gè)對象,但是可以看做是是一種工具,一種從未見過的工具,解決的是Nodejs異步接口串行執(zhí)行導(dǎo)致的回調(diào)地獄問題,它本身對代碼邏輯沒有影響

廢話少說,直接上代碼:

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()

})

與之前的異步代碼不同的是,我們在異步函數(shù)外層包了一層返回promise對象的函數(shù),promise對象向自己包裹的函數(shù)里傳入了兩個(gè)函數(shù)參數(shù)resolve和reject,在異步函數(shù)內(nèi)部通過調(diào)用resolve向外傳遞操作成功數(shù)據(jù),調(diào)用reject向外傳遞錯(cuò)誤信息

關(guān)于promise對象使用的語法牽涉到es6的最新規(guī)范和函數(shù)式編程,這里不做詳細(xì)介紹

接著我們調(diào)用這個(gè)函數(shù),鏈?zhǔn)秸{(diào)用promise對象提供的接口函數(shù).then(function(res){//TODO})將異步函數(shù)向外傳遞的值取出來,使用.catch()捕獲內(nèi)部傳遞的錯(cuò)誤。

最基本的promise用法大致就是這樣,但這樣看依然沒明白它如何避免了回調(diào)地獄,這里我們使用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()

代碼這樣寫應(yīng)該會(huì)看的比較清楚,我們把每個(gè)異步函數(shù)都封裝在promise對象里面,然后通過promise的鏈?zhǔn)秸{(diào)用來傳遞數(shù)據(jù),從而避免了回調(diào)地獄。

這樣的代碼可讀性和維護(hù)性要好上不少,但很顯然代碼量增加了一些,就是每個(gè)函數(shù)的封裝過程,但node里的util庫中的promisify函數(shù)提供了將滿足node回調(diào)規(guī)則的函數(shù)自動(dòng)轉(zhuǎn)換為promise對象的功能,若沒有對異步操作的復(fù)雜訂制,可以使用這個(gè)函數(shù)減少代碼量

雖然promise相對于原生的回調(diào)來說已經(jīng)是相當(dāng)良好的編程體驗(yàn),但對于追求完美的程序員來說,這依舊不夠優(yōu)美,于是es規(guī)范在演進(jìn)的過程中又推出了新的異步編程方式

Generator

Generator并不是最終的異步解決方案,而是Promise向最終方案演進(jìn)的中間產(chǎn)物,但是其中利用到的迭代器設(shè)計(jì)模式值得我們學(xué)習(xí)和參考。這里不對這種方法多做介紹,因?yàn)橛辛薬sync,一般就不再使用Generator了。

async/await

Async/Await其實(shí)是Generator的語法糖,但是因?yàn)槭褂玫臅r(shí)候使異步編程似乎完全變成了同步編程,體驗(yàn)異常的好,而且這是官方推出的最新規(guī)范,所以廣受推崇,這里就對如何使用它進(jìn)行一些介紹說明。

先看Async的語法,用起來真的是相當(dāng)簡單

async function main(){

const ret = await someAsynFunc();

const ret2 = await otherAsyncFunc(ret)

return someSyncFunc(ret,ret2)

}

定義一個(gè)函數(shù),函數(shù)申明前加上一個(gè)async關(guān)鍵字,說明這個(gè)函數(shù)內(nèi)部有需要同步執(zhí)行的異步函數(shù)

此函數(shù)需要同步執(zhí)行的異步函數(shù)必須返回的是promise對象,就是我們之前用promise包裹的那個(gè)形式

在需同步執(zhí)行的異步函數(shù)調(diào)用表達(dá)式前加上await關(guān)鍵字,這時(shí)函數(shù)會(huì)同步執(zhí)行并將promise對象resolve出來的數(shù)據(jù)傳遞給等號之前的變量

我們再使用async/await改寫promisify.js文件

//async/await.js

const promisify = require('util').promisify //引入promisify函數(shù),簡化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學(xué)習(xí)曲線比較陡峭。

這種方式寫出的代碼可讀性可維護(hù)性可以說是非常強(qiáng),完全沒有之前的回調(diào)地獄或者原生promise帶來的副作用。

進(jìn)階

試想這么一種場景:

我們需要從多個(gè)數(shù)據(jù)庫中讀取數(shù)據(jù),讀取完成的順序無所謂.

我們需要在多次數(shù)據(jù)讀取全部完成之后再從每個(gè)數(shù)據(jù)中篩選出某種相同的屬性

再對這些屬性進(jìn)行一些自定義操作,得到結(jié)果數(shù)據(jù)

最后將結(jié)果數(shù)據(jù)插入某個(gè)數(shù)據(jù)庫

假設(shè)每一步的具體實(shí)現(xiàn)函數(shù)全部已經(jīng)編寫完成,所有異步的函數(shù)都已經(jīng)封裝成promise,那么用原生js組裝以上四步代碼需要怎么寫?

我粗略估計(jì)一下可能需要二十行左右,而且代碼的可讀性不會(huì)很好,這里我簡單介紹一個(gè)庫:RxJS,中文名為響應(yīng)式JS。

響應(yīng)式編程發(fā)展已久,許多語言都有實(shí)現(xiàn)相應(yīng)的庫,對應(yīng)的名字也以Rx開頭,比如RxJava

不說RxJS的設(shè)計(jì)原理,它的使用都牽涉到多種設(shè)計(jì)模式和技術(shù),包括觀察者模式,管道模式,函數(shù)式編程等,可以說這是一個(gè)上手難度相當(dāng)大的技術(shù),但它帶來的編程體驗(yàn)是相當(dāng)?shù)暮?,這里我給出使用RxJS實(shí)現(xiàn)以上四步的代碼

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

const promiseArray = require('./readDatabase')//封裝好的讀數(shù)據(jù)庫函數(shù)數(shù)組

const myfilter = require('./filter')//數(shù)據(jù)屬性過濾函數(shù)

const operation = require('./operation')//自定義的邏輯操作

const insert = require('./insert')//數(shù)據(jù)庫插入函數(shù)

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

.filter(myfilter)

.switchMap(operations)

.subscribe(insert)

除去將自定義的函數(shù)引入,四步操作每步只對應(yīng)一行代碼,這樣寫真的是非常簡潔。

我們平時(shí)常用的,甚至是不常用的對數(shù)據(jù)的操作,基本都能在RxJS官網(wǎng)里都能找到封裝好的API,有興趣的同學(xué)可以多關(guān)注這個(gè)庫,就我自己平時(shí)開發(fā)時(shí)的體驗(yàn)來看,這個(gè)庫是相當(dāng)?shù)暮糜茫且幸欢ǖ男睦頊?zhǔn)備,因?yàn)榇_實(shí)有一些難度。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3565

    瀏覽量

    93536
  • python
    +關(guān)注

    關(guān)注

    55

    文章

    4768

    瀏覽量

    84376
  • nodejs
    +關(guān)注

    關(guān)注

    0

    文章

    20

    瀏覽量

    4218

原文標(biāo)題:干貨 | Nodejs異步編程詳解

文章出處:【微信號:ZTEdeveloper,微信公眾號:中興開發(fā)者社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    關(guān)于編程一些問題

    開始學(xué)習(xí)編程的時(shí)候沒想那么多,可是現(xiàn)在編的程序大了之后開始考慮一些算法的東西 因?yàn)?b class='flag-5'>編程的時(shí)候都是在大腦里想的邏輯順序 然后就直接編程 可是隨著程序代碼量大了之后,自己越來越困難了
    發(fā)表于 10-30 20:59

    關(guān)于AVR的一些編程問題,請教請教

    關(guān)于AVR的一些編程問題,請教請教
    發(fā)表于 05-19 09:39

    講解Matlab的一些編程語句

    第5章 Matlab簡易使用之常用編程語句本期教程主要是講解Matlab的一些編程語句。目錄第5章 Matlab簡易使用之常用編程語句5.1
    發(fā)表于 08-17 07:45

    有線IAP用戶程序升級的一些心得分享

    如果你沒有時(shí)間,想吃“快餐”,請直接移步至文末。在上篇博文中,我介紹了我有線IAP用戶程序升級的一些心得,有線升級并不是我的目的,無線才是,所以就有了這篇文章。這篇文章介紹
    發(fā)表于 02-16 06:33

    關(guān)于stm32的一些簡單的介紹

    #序言本文章是關(guān)于stm的一些簡單的介紹,全部都是個(gè)人學(xué)習(xí)的一些經(jīng)驗(yàn)總結(jié),分享給想要自學(xué)stm32的朋友們用于入門。其中部分內(nèi)容借鑒于《stm32中文參考手冊》和《cortex-m3權(quán)
    發(fā)表于 02-24 06:30

    設(shè)計(jì)開關(guān)電源的一些關(guān)鍵問題

    對于開關(guān)電源的噪聲,除了芯片本身,Layout的設(shè)計(jì)最為重要,記錄一些相關(guān)的技巧。不少關(guān)于EMI的觀念具有通用性。下面我們談?wù)?b class='flag-5'>關(guān)于開關(guān)電源設(shè)計(jì)的一些
    發(fā)表于 12-11 17:09 ?3155次閱讀

    LTE的其它一些關(guān)鍵技術(shù)介紹

    以前寫論文收集的一些資料,學(xué)習(xí)射頻電路、無線通信技術(shù)的好資料?。。∮绕涫?b class='flag-5'>關(guān)于4G/LTE方面的學(xué)習(xí)?。?!
    發(fā)表于 06-27 15:11 ?0次下載

    關(guān)于PID一些常用知識(shí)

    本文檔詳細(xì)介紹分析了關(guān)于PID的一些常用知識(shí)
    發(fā)表于 08-29 14:22 ?2次下載

    關(guān)于STM32f1和f4編程一些問題解決方案

    關(guān)于STM32f1和f4編程一些問題
    發(fā)表于 04-03 15:31 ?5次下載

    樹莓派利用Nodejs+Express開發(fā)關(guān)于硬件監(jiān)控的服務(wù)討論

    當(dāng)前Nodejs+Express在web應(yīng)用開發(fā)領(lǐng)域很是紅火,但是否能用其開發(fā)關(guān)于硬件的服務(wù)?回答當(dāng)然是肯定的,本文就將簡要介紹相關(guān)一些開發(fā)要點(diǎn)。
    發(fā)表于 04-22 15:15 ?1493次閱讀

    關(guān)于C語言的一些特殊功能介紹

    C語言之所以那么受歡迎,除了C語言歷史悠久之外,還有它具有一些編程語言沒有的功能。那么,今年我們就來了解下C語言的一些特殊功能。
    的頭像 發(fā)表于 08-18 15:31 ?2374次閱讀
    <b class='flag-5'>關(guān)于</b>C語言的<b class='flag-5'>一些</b>特殊功能<b class='flag-5'>介紹</b>

    圖像捕獲是AI中最難的問題之

     Azure AI的CVP Eric Boyd在接受Engadget采訪時(shí)說:“圖像捕獲是AI中最難的問題之。它不僅代表理解場景中的對象,還包括它們之間的交互方式,以及如何描述它們。
    的頭像 發(fā)表于 10-15 14:03 ?1684次閱讀

    電腦的一些硬件問題講解

    本文檔的主要內(nèi)容詳細(xì)介紹的是電腦的一些硬件問題講解。
    發(fā)表于 10-20 08:00 ?14次下載
    電腦的<b class='flag-5'>一些</b>硬件問題<b class='flag-5'>講解</b>

    NEON編程中的一些常見優(yōu)化技巧

      讀過上篇文章“ARM NEON快速上手指南”之后,相信你已經(jīng)對ARM NEON編程有了基本的認(rèn)識(shí)。但在真正利用ARM NEON優(yōu)化程序性能時(shí),還有很多編程技巧和注意事項(xiàng)。本文將結(jié)合本人的
    的頭像 發(fā)表于 12-12 09:11 ?1897次閱讀

    nodejs 后端技術(shù)介紹

    筆者最開始學(xué)的后端技術(shù)是 python 的 Django 框架,由于很久沒有使用過 python 語法,便想著了解一些 nodejs 的后端技術(shù)。下面將最近的收獲總結(jié)下。
    的頭像 發(fā)表于 05-05 16:41 ?1057次閱讀