NodeJs通過(guò)async/await處理異步的方法
場(chǎng)景
遠(yuǎn)古時(shí)代
我們?cè)诰帉?xiě)express后臺(tái),經(jīng)常要有許多異步IO的處理。在遠(yuǎn)古時(shí)代,我們都是用chunk函數(shù)處理,也就是我們最熟悉的那種默認(rèn)第一個(gè)參數(shù)是error的函數(shù)。我們來(lái)模擬一個(gè)Mongo數(shù)據(jù)庫(kù)的操作,感受一下。
mongoDb.open(function(err, db){
if(!err){
db.collection("users", function(err, collection){
if(!err){
let person = {name: "yika", age: 20};
collection.insert(person, function(err, result){
if(!err){
console.log(result);
}
});
}
})
}
});
這個(gè)也就是被我們所詬病的callback hell,一堆橫向金字塔,如果將回調(diào)拆分成函數(shù),則會(huì)變得非常支離破碎。為了防止到惡心到大家,我甚至沒(méi)有寫(xiě)關(guān)于錯(cuò)誤的處理,正常來(lái)說(shuō),每一個(gè)異步的操作都需要都它的error進(jìn)行相應(yīng)的顯示或處理的。
Promise時(shí)代
后來(lái)進(jìn)入了好一點(diǎn)的時(shí)代就是Promise,我們也可以稱作鏈?zhǔn)讲僮鳌jP(guān)于Promise,我也是之前有專門(mén)寫(xiě)過(guò)一系列的博文,有興趣可以回頭翻一下。這里來(lái)看看,將以上改寫(xiě)之后的狀況。
let person = {name: "yika"};
mongoDb
.open()
.then(function(database){
return database.collection("users");
})
.then(function(collection){
return collection.insert(person);
})
.then(function(result){
console.log(result);
})
.catch(function(e){
throw new Error(e);
})
我們可以看到,我們將金字塔已經(jīng)平鋪成一條線狀結(jié)構(gòu)了。相比之前惡心難以維護(hù)的chunk函數(shù),變成了promise函數(shù),并且錯(cuò)誤的處理也變得十分優(yōu)雅。但是我們?nèi)匀徊豢珊鲆暷承﹩?wèn)題,例如我們必須忍受各個(gè)邏輯被一個(gè)又一個(gè)的then()包裹起來(lái),每一個(gè)函數(shù)都有其獨(dú)立的作用域,如果為了共享某個(gè)數(shù)據(jù)就必須掛在最外層,最重要的還是,它與我們熟悉的同步編程仍然有差別。
Generator時(shí)代
TJ大神,借著ES6的Generator迭代器,最早實(shí)現(xiàn)了異步編程同步化的功能,也就是最為我們所熟知的co庫(kù)。我們通過(guò)co(function *(){})可以使函數(shù)內(nèi)部通過(guò)迭代器來(lái)控制。而co在這里則是充當(dāng)了啟動(dòng)器的角色。關(guān)于Generator和co我在之前的博文也同樣說(shuō)過(guò)。
let co = require("co");
co(function *(){
let db, collection, result;
let person = {name: "yika"};
try{
db = yield mongoDb.open();
collection = yield db.collection("users");
result = yield collection.insert(person);
}catch(e){
console.error(e.message);
}
console.log(result);
});
我們已經(jīng)非常接近同步編程了,在co包裹的函數(shù)內(nèi)部,只有一個(gè)異步執(zhí)行完畢,才會(huì)繼續(xù)執(zhí)行下面的代碼。并且錯(cuò)誤的處理也是通過(guò)try and catch進(jìn)行實(shí)現(xiàn)的。不過(guò)我們不得不承認(rèn)的是,迭代器終究不是為異步而存在的。里面的yield和*的語(yǔ)義也并不代表的就是異步函數(shù)標(biāo)志。并且迭代器是需要co去驅(qū)動(dòng)的,它和我們想象中的函數(shù)多少有一點(diǎn)點(diǎn)不同。
async/await時(shí)代
我們關(guān)注到ES7的async/await,才發(fā)現(xiàn)這才是我們想要的!我們將上面的代碼小小改寫(xiě)一下。
async function insertData(person){
let db, collection, result;
try{
db = await mongoDb.open();
collection = await db.collection("users");
result = await collection.insert(person);
}catch(e){
console.error(e.message);
}
console.log(result);
}
insertData({name: "yika"});
我們可以看到inserData是一個(gè)真正的函數(shù),是我們可以直接去調(diào)用而無(wú)需啟動(dòng)器驅(qū)動(dòng)的。當(dāng)然內(nèi)部我們也可以感受到處理yield變成了await以外,并沒(méi)有很大區(qū)別。async/await,更符合我們異步編程的語(yǔ)義。
那么問(wèn)題來(lái)了,how to use it?
使用
我們一開(kāi)始就說(shuō)過(guò),babel已經(jīng)支持async的transform了,所以我們使用的時(shí)候引入babel就行。當(dāng)然server端和browser端,可以有不同的處理方法。在開(kāi)始之前我們需要引入以下的package,preset-stage-3里就有我們需要的async/await的編譯文件。
$ npm install babel-core --save $ npm install babel-preset-es2015 --save $ npm install babel-preset-stage-3 --save
Browser端
Babel一開(kāi)始的出現(xiàn)就是為了讓舊瀏覽器也能支持新的ES6特性,提升我們的開(kāi)發(fā)體驗(yàn)。所以在Babel一開(kāi)始就是可以通過(guò)babel-cli終端進(jìn)行編譯的?;蛘咭隻abel文件在瀏覽器端進(jìn)行編譯。當(dāng)然這些都不是我最推薦的,所以我就帶過(guò)不說(shuō)啦。在前端靜態(tài)資源配置里,webpack是現(xiàn)在比較好的解決方案,它支持靜態(tài)資源的模塊依賴,打包合并,還有語(yǔ)言的預(yù)處理,當(dāng)然在這里我們就是指babel的處理。
// webpack.config.js
// 省略上面的文件輸入輸出的配置,直接看模塊加載器的配置
module: {
loaders: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: "babel",
query: {
presets: ['es2015', 'stage-3']
}
},
]
}
這樣我們就可以愉快的使用了。
Server端
相對(duì)來(lái)說(shuō),后端比前端需要處理的異步IO地方多得多,也是更加需要這個(gè)。那我們?cè)赟erver端又如何引入babel呢?
其實(shí)最簡(jiǎn)單也是最麻煩的方法就是,直接把js文件通過(guò)babel編譯出新的文件再來(lái)使用。當(dāng)然也就免不了冗余文件了,眼不見(jiàn)心不煩,還是換一個(gè)方法吧。
我們使用官方提供的require hook方法,顧名思義就是通過(guò)require進(jìn)來(lái)后,接下來(lái)的文件進(jìn)行require的時(shí)候都會(huì)經(jīng)過(guò)Babel的處理。因?yàn)槲覀冎繡ommonJs是同步的模塊依賴,所以也是可行的方法。我們需要多一個(gè)用于啟動(dòng)的js文件,一個(gè)真正執(zhí)行程序的js文件。
// index.js
// 用于引入babel,并且啟動(dòng)app.js
require("babel-core/register");
require("./app.js");
配置完hook之后,我們就配置babel的.babelrc文件,它是一個(gè)json格式的文件。es2015看情況配置,如果是已經(jīng)是Node5.0版本,就無(wú)需再進(jìn)行編譯。
{
"presets": ["stage-3", "es2015"]
}
最后我們的異步函數(shù)代碼,寫(xiě)在app.js里即可。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Nodejs為什么選擇javascript為載體語(yǔ)言
準(zhǔn)備寫(xiě)一個(gè)NodeJS方面的系列文章,由淺入深,循序漸進(jìn),秉承的理念是重思想,多實(shí)踐,勤能補(bǔ)拙,貴在堅(jiān)持。本文首先來(lái)點(diǎn)基礎(chǔ)知識(shí)的開(kāi)篇吧。2015-01-01
Node.js中path.join()優(yōu)勢(shì)例舉分析
在本篇文章里小編給大家整理的是一篇關(guān)于Node.js中path.join()優(yōu)勢(shì)例舉分析,有興趣的朋友們可以學(xué)習(xí)下。2021-08-08
如何在Nestjs和Vue3中使用socket.io示例詳解
這篇文章主要為大家介紹了如何在Nestjs和Vue3中使用socket.io示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Node.js API詳解之 tty功能與用法實(shí)例分析
這篇文章主要介紹了Node.js API詳解之 tty功能與用法,結(jié)合實(shí)例形式分析了Node.js API中tty的基本功能、用法及終端操作相關(guān)使用技巧,需要的朋友可以參考下2020-04-04
Node中完整的?node?addon?實(shí)現(xiàn)流程
這篇文章主要介紹了Node中完整的node?addon實(shí)現(xiàn)流程,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
我的Node.js學(xué)習(xí)之路(三)--node.js作用、回調(diào)、同步和異步代碼 以及事件循環(huán)
本篇文章主要介紹了node.js的幾個(gè)重要的知識(shí)點(diǎn):node.js作用、回調(diào)、同步和異步代碼 以及事件循環(huán)2014-07-07
詳解用node搭建簡(jiǎn)單的靜態(tài)資源管理器
本篇文章主要介紹了詳解用node搭建簡(jiǎn)單的靜態(tài)資源管理器,主要用node的fs模塊,自己手寫(xiě)一個(gè)簡(jiǎn)單的靜態(tài)資源管理器。有興趣的可以了解一下2017-08-08
Node學(xué)習(xí)筆記:Node.js安裝及環(huán)境配置 史詩(shī)級(jí)詳細(xì)版【含測(cè)試與鏡像說(shuō)明】
這篇文章主要介紹了Node學(xué)習(xí)筆記之Node.js安裝及環(huán)境配置方法,詳細(xì)分析了node.js的基本安裝、配置、環(huán)境變量設(shè)置、以及環(huán)境測(cè)試與鏡像使用說(shuō)明,需要的朋友可以參考下2023-05-05

