<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
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

    文章

    3450

    瀏覽量

    92715
  • python
    +關注

    關注

    52

    文章

    4698

    瀏覽量

    83610
  • nodejs
    +關注

    關注

    0

    文章

    19

    瀏覽量

    4190

原文標題:干貨 | Nodejs異步編程詳解

文章出處:【微信號:ZTEdeveloper,微信公眾號:中興開發者社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    介紹一些國外關于高速PCB設計的技術書籍和資料嗎?

    介紹一些國外關于高速PCB設計的技術書籍和資料嗎?
    發表于 09-06 08:40

    關于編程一些問題

    開始學習編程的時候沒想那么多,可是現在編的程序大了之后開始考慮一些算法的東西 因為編程的時候都是在大腦里想的邏輯順序 然后就直接編程 可是隨著程序代碼量大了之后,自己越來越困難了
    發表于 10-30 20:59

    請教關于GAL的一些問題

    由鑒相器、環路濾波器和壓控振蕩器三部分組成,但都是模擬的,網上也有關于數字鑒相器以及數字濾波器的介紹,我有個想法,想用GAL芯片做個鑒相電路,用
    發表于 10-31 10:07

    想找一些關于小車的

    想找一些關于智能小車的經典的詳細做法,求指導(如循跡、避障、尋聲、尋光功能的小車),很多資料不合心意,求詳細且讓人明白的資料
    發表于 07-31 11:13

    介紹一些簡單的項目

    學51單片機已經個多月,簡單的操作感覺還好。 想找一些簡單的項目下,在項目中學習,求介紹一些
    發表于 08-18 12:35

    關于編程一些問題?

    本人對編程來說,是個小白的,請問下,我怎么能夠快速成長成為編程高手,能夠自己寫代碼的?有誰能提供一些忠實的意見的嗎?謝謝,還請過來人能夠多多談談的,謝謝,經驗分享下,不勝感激,是不
    發表于 06-03 22:35

    關于AVR的一些編程問題,請教請教

    關于AVR的一些編程問題,請教請教
    發表于 05-19 09:39

    一些關于SP3485的資料,

    一些關于SP3485的資料,我是個新人,領導讓自己設計個板子,需要用3485 ,查了資料還是不大明白,希望大神們可以分享一些
    發表于 08-09 22:13

    28335有沒有一些關于匯編的資料?

    關于28335有沒有一些關于匯編的資料?能否介紹
    發表于 06-16 07:38

    講解Matlab的一些編程語句

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

    異步信號的處理真的有那么神秘嗎

    問題,不過請注意,今后的這些關于異步信號處理的文 章里將會重點從工程實踐的角度出發,以一些特權同學遇到過的典型案例的設計為依托,從代碼的角度來剖析一些特權同學認為經典的跨時鐘域信號處理
    發表于 11-04 08:03

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

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

    關于stm32的一些簡單的介紹

    #序言本文章是關于stm的一些簡單的介紹,全部都是個人學習的一些經驗總結,分享給想要自學stm32的朋友們用于入門。其中部分內容借鑒于《stm32中文參考手冊》和《cortex-m3權
    發表于 02-24 06:30

    分享關于位操作一些筆記

    分享關于位操作一些筆記:、位操作簡單介紹首先,以下是按位運算符:在嵌入式編程中,常常需要對一些
    發表于 02-25 08:01

    介紹一些PLC編程中常用的基礎邏輯

    以保證速度。PLC編程樣,建筑中的預制標準件就像我們編程中的庫函數,可以使我們在編程的過程中節約大量的時間和精力?! ∠旅婢徒o大家介紹
    發表于 03-13 17:20
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>