Node.js 異步編程之 Callback介紹

原文:http://www.jb51.net/article/63070.htm

-------------------------------------

Node.js 基于 JavaScript 引擎 v8,是單線程的。Node.js 采用了與通常 Web 上的 JavaScript 異步編程的方式來處理會造成阻塞的I/O操作。在 Node.js 中讀取文件、訪問數據庫、網絡請求等等都有可能是異步的。對于 Node.js 新人或者從其他語言背景遷移到 Node.js 上的開發者來說,異步編程是比較痛苦的一部分。本章將由淺入深為大家講解 Node.js 異步編程的方方面面。從最基礎的 callback 到 thunk、Promise、co 直到 ES7 計劃的 async/await。

首先我們先從一個具體的異步編程的例子說起。

獲取多個 ip 所在地的天氣信息

在 ip.json 這個文件中,有一個數組我們存放了若干個 ip 地址,分別來自不同的地方的不同訪問者,內容如下:

復制代碼 代碼如下:

// ip.json
["115.29.230.208", "180.153.132.38", "74.125.235.224", "91.239.201.98", "60.28.215.115"]

希望可以每一個 ip 所在地當前的天氣。將結果輸出到 weather.json 這個文件中各式如下:
復制代碼 代碼如下:

// weather.json
[
? { "ip": "115.29.230.208", "weather": "Clouds", "region": "Zhejiang" },
? { "ip": "180.153.132.38", "weather": "Clear", "region": "Shanghai" },
? { "ip": "74.125.235.224", "weather": "Rain", "region": "California" },
? { "ip": "60.28.215.115", "weather": "Clear", "region": "Tianjin" }
]

整理思路,我們分成以下幾步來完成:

1.讀取 ip 地址;
2.根據 ip 地址獲取 ip 所在地的地理位置;
3.根據地理位置查詢當地的天氣;
4.將結果寫入到 weather.json 文件中。

這些步驟都是異步的(讀寫文件可以同步,但作為示例,都用異步)。

callback

首先我們嘗試不借助任何庫,試著以 Node.js API 通常提供的方式——專遞一個 callback 作為異步回調——來實現。我們將借助三個基礎模塊:

1.fs:從文件 ip.json 讀取 IP 列表;把結果寫入到文件中;
2.request:用來發送 HTTP 請求,根據 IP 地址獲取 geo 數據,再通過 geo 數據獲取天氣數據;
3.querystring:用來組裝發送請求的 url 參數。

新建一個 callback.js 文件,引入這幾個模塊:

復制代碼 代碼如下:

// callback.js
var fs = require('fs')
var request = require('request')
var qs = require('querystring')

讀取文件中的 IP 列表,調用 fs.readFile 讀取文件內容,再通過 JSON.parse 來解析 JSON 數據:

復制代碼 代碼如下:

...
function readIP(path, callback) {
? fs.readFile(path, function(err, data) {
??? if (err) {
????? callback(err)
??? } else {
????? try {
??????? data = JSON.parse(data)
??????? callback(null, data)
????? } catch (error) {
??????? callback(error)
????? }
??? }
? })
}
...

接著就是使用 IP 來獲取geo,我們使用 request 來請求一個開放的 geo 服務:

復制代碼 代碼如下:

...
function ip2geo(ip, callback) {
? var url = 'http://www.telize.com/geoip/' + ip
? request({
??? url: url,
??? json: true
? }, function(err, resp, body) {
??? callback(err, body)
? })
}
...

使用 geo 數據來獲取 weather:

復制代碼 代碼如下:

...
function geo2weather(lat, lon, callback) {
? var params = {
??? lat: lat,
??? lon: lon,
??? APPID: '9bf4d2b07c7ddeb780c5b32e636c679d'
? }
? var url = 'http://api.openweathermap.org/data/2.5/weather?' + qs.stringify(params)
? request({
??? url: url,
??? json: true,
? }, function(err, resp, body) {
??? callback(err, body)
? })
}
...

現在我們已經獲取 geo、獲取 weather 的接口,接下來我們還有稍微復雜的問題要處理,因為 ip 有多個,所以我們需要并行地去讀取 geo 已經并行地讀取 weather 數據:
復制代碼 代碼如下:

...
function ips2geos(ips, callback) {
? var geos = []
? var ip
? var remain = ips.length
? for (var i = 0; i < ips.length; i++) {
??? ip = ips[i];
??? (function(ip) {
????? ip2geo(ip, function(err, geo) {
??????? if (err) {
????????? callback(err)
??????? } else {
????????? geo.ip = ip
????????? geos.push(geo)
????????? remain--
??????? }
??????? if (remain == 0) {
????????? callback(null, geos)
??????? }
????? })
??? })(ip)
? }
}

function geos2weathers(geos, callback) {
? var weathers = []
? var geo
? var remain = geos.length
? for (var i = 0; i < geos.length; i++) {
??? geo = geos[i];
??? (function(geo) {
????? geo2weather(geo.latitude, geo.longitude, function(err, weather) {
??????? if (err) {
????????? callback(err)
??????? } else {
????????? weather.geo = geo
????????? weathers.push(weather)
????????? remain--
??????? }
??????? if (remain == 0) {
????????? callback(null, weathers)
??????? }
????? })
??? })(geo)
? }
}
...

ips2geos 和 geos2weathers 都使用了一種比較原始的方法,remain 來計算等待返回的個數,remain 為 0 表示并行請求結束,將處理結果裝進一個數組返回。

最后就是將結果寫入到 weather.json 文件中:

復制代碼 代碼如下:

...
function writeWeather(weathers, callback) {
? var output = []
? var weather
? for (var i = 0; i < weathers.length; i++) {
??? weather = weathers[i]
??? output.push({
????? ip: weather.geo.ip,
????? weather: weather.weather[0].main,
????? region: weather.geo.region
??? })
? }
? fs.writeFile('./weather.json', JSON.stringify(output, null, '? '), callback)
}
...

組合上面這些函數,我們就可以實現我們的目標:

復制代碼 代碼如下:

...
function handlerError(err) {
? console.log('error: ' + err)
}

readIP('./ip.json', function(err, ips) {
? if (err) {
??? handlerError(err)
? } else {
??? ips2geos(ips, function(err, geos) {
????? if (err) {
??????? handlerError(err)
????? } else {
??????? geos2weathers(geos, function(err, weathers) {
????????? if (err) {
??????????? handlerError(err)
????????? } else {
??????????? writeWeather(weathers, function(err) {
????????????? if (err) {
??????????????? handlerError(err)
????????????? } else {
??????????????? console.log('success!')
????????????? }
??????????? })
????????? }
??????? })
????? }
??? })
? }
})

哈哈,你媽這嵌套,你可能覺得這就是 JavaScript 異步的問題,說真的,嵌套不是 JavaScript 異步的真正問題所在。上面這段代碼我們可以下面這樣寫:

復制代碼 代碼如下:

...
function ReadIPCallback(err, ips) {
? if (err) {
??? handlerError(err)
? } else {
??? ips2geos(ips, ips2geosCallback)
? }
}

function ips2geosCallback(err, geos) {
? if (err) {
??? handlerError(err)
? } else {
??? geos2weathers(geos, geos2weathersCallback)
? }
}

function geos2weathersCallback(err, weathers) {
? if (err) {
??? handlerError(err)
? } else {
??? writeWeather(weathers, writeWeatherCallback)
? }
}

function writeWeatherCallback(err) {
? if (err) {
??? handlerError(err)
? } else {
??? console.log('success!')
? }
}

readIP('./ip.json', ReadIPCallback)

好了,這是我們 callback.js 的全部內容。運行:

復制代碼 代碼如下:

node callback.js

將會生成 weater.json 文件:
復制代碼 代碼如下:

[
? {
??? "ip": "180.153.132.38",
??? "weather": "Clear",
??? "region": "Shanghai"
? },
? {
??? "ip": "91.239.201.98",
??? "weather": "Clouds"
? },
? {
??? "ip": "60.28.215.115",
??? "weather": "Clear",
??? "region": "Tianjin"
? },
? {
??? "ip": "74.125.235.224",
??? "weather": "Clouds",
??? "region": "California"
? },
? {
??? "ip": "115.29.230.208",
??? "weather": "Clear",
??? "region": "Zhejiang"
? }
]

那正真的問題是什么?

當然是異步的問題啦,異步本質上要處理三個事情:

1.異步操作什么時候結束,需要通知回來,Callback 是一種方案;
2.異步產生的結果需要傳遞回來,Callback 接受一個 data 參數,把數據傳回來;
3.異步如果出錯了怎么辦?Callback 接受 一個 err 參數,把錯誤傳回來。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/539557.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/539557.shtml
英文地址,請注明出處:http://en.pswp.cn/news/539557.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

php雙向鏈表+性能,PHP雙向鏈表定義與用法示例

本文實例講述了PHP雙向鏈表定義與用法。分享給大家供大家參考&#xff0c;具體如下&#xff1a;由于需要對一組數據多次進行移動操作&#xff0c;所以寫個雙向鏈表。但對php實在不熟悉&#xff0c;雖然測試各個方法沒啥問題&#xff0c;就是不知道php語言深層的這些指針和unset…

反擊爬蟲,前端工程師的腦洞可以有多大?

對于一張網頁&#xff0c;我們往往希望它是結構良好&#xff0c;內容清晰的&#xff0c;這樣搜索引擎才能準確地認知它。 而反過來&#xff0c;又有一些情景&#xff0c;我們不希望內容能被輕易獲取&#xff0c; 前言 比方說電商網站的交易額&#xff0c;教育網站的題目等。因為…

Spring與Struts框架整合

Spring&#xff0c;負責對象對象創建 Struts&#xff0c;用Action處理請求 Spring與Struts框架整合&#xff0c;關鍵點&#xff1a;讓struts框架action對象的創建&#xff0c;交給spring完成&#xff01; 1.步驟&#xff1a; 引入jar文件 1&#xff09;引入struts .jar相關文件…

esxi能直通的顯卡型號_顯卡刷bios教程

一般來說顯卡默認的出廠bios就已經很穩定&#xff0c;如果沒有特殊情況下建議不要刷顯卡bios。一般而言部分網友刷顯卡BIOS目的是開核或超頻&#xff0c;那么對于一個不會刷顯卡bios的網友來說肯定會問顯卡怎么刷bios類似的問題&#xff0c;那么本文這里就說一下有關顯卡怎么刷…

關于Linux網卡調優之:RPS (Receive Packet Steering)

昨天在查LVS調度均衡性問題時&#xff0c;最終確定是 persistence_timeout 參數會使用IP哈希。目的是為了保證長連接&#xff0c;即一定時間內訪問到的是同一臺機器。而我們內部系統&#xff0c;由于出口IP相對單一&#xff0c;所以總會被哈希到相同的RealServer。 過去使用LVS…

footer.php置底,CSS五種方式實現Footer置底

頁腳置底(Sticky footer)就是讓網頁的footer部分始終在瀏覽器窗口的底部。當網頁內容足夠長以至超出瀏覽器可視高度時&#xff0c;頁腳會隨著內容被推到網頁底部&#xff1b;但如果網頁內容不夠長&#xff0c;置底的頁腳就會保持在瀏覽器窗口底部。方法一&#xff1a;將內容部分…

安卓adapter適配器作用_自帶安卓系統的便攜屏,能玩出什么花樣?

之前說到去年出差太多&#xff0c;平常就把便攜屏帶上了。之前也說了如果是像筆者這樣的出差狗也知道&#xff0c;托運需要提前去機場一路著急忙慌&#xff0c;不托運只需要打印登機牌(紙質才給報銷)排隊安檢登機就完了。有的時候可以把標準顯示器來回寄&#xff0c;只要包裝強…

Gradle插件學習筆記(二)

之前介紹了Gradle插件的開發&#xff0c;這次會對功能進行一部分拓展&#xff0c;建議沒有讀過第一篇文章的朋友&#xff0c;先看一下Gradle插件學習筆記&#xff08;一&#xff09; Extension 之前的文章提到過&#xff0c;如何編寫一個插件&#xff0c;但是并不能通過外面傳遞…

php抽象類繼承抽象類,PHP面向對象程序設計高級特性詳解(接口,繼承,抽象類,析構,克隆等)...

本文實例講述了PHP面向對象程序設計高級特性。分享給大家供大家參考&#xff0c;具體如下&#xff1a;靜態屬性class StaticExample {static public $aNum 0; // 靜態共有屬性static public function sayHello() { // 靜態共有方法print "hello";}}print StaticExam…

Typora markdown公式換行等號對齊_Typora編寫博客格式化文檔的最佳軟件

Typora-編寫博客格式化文檔的最佳軟件Typora 不僅是一款支持實時預覽的 Markdown 文本編輯器&#xff0c;而且還支持數學公式、代碼塊、思維導圖等功能。它有 OS X、Windows、Linux 三個平臺的版本&#xff0c;是完全免費的。作為技術人員或者專業人員&#xff0c;使用Markdown…

Bootstrap靜態cdn

百度的靜態資源庫的 CDN 服務http://cdn.code.baidu.com/ &#xff0c;訪問速度更快、加速效果更明顯、沒有速度和帶寬限制、永久免費,引入代碼如下&#xff1a; <!-- 新 Bootstrap 核心 CSS 文件 --> <link href"http://apps.bdimg.com/libs/bootstrap/3.3.0/…

php復習,PHP排序算法的復習和總結

直接上代碼吧&#xff01;/** 插入排序(一維數組)* 每次將一個待排序的數據元素&#xff0c;插入到前面已經排好序的數列中的適當的位置&#xff0c;使數列依然有序&#xff1b;直到待排序的數據元素全部插入完成為止。*/function insertSort($arr){if(!is_array($arr) || coun…

docker-machine

vbox安裝 sudo /sbin/vboxconfig &#xfffc; yum install gcc make yum install kernel-devel-3.10.0-514.26.2.el7.x86_64 轉載于:https://www.cnblogs.com/yixiaoyi/p/dockermachine.html

intention lock_寫作技巧:你寫出來的情節有用嗎?好情節的原則——LOCK系統

讀者喜歡一本小說的原因只有一個&#xff1a;很棒的故事。——Donald Maass來&#xff0c;話筒對準這位小作家&#xff0c;請問你是如何構思故事的&#xff1f;是習慣于現在腦海中把故事都想好了&#xff0c;才開始寫作&#xff1f;還是習慣于臨場發揮&#xff0c;喜歡一屁股坐…

zookeeper基本操作

1.客戶端連接 [txtest1 bin]$ jps 23433 Jps 23370 QuorumPeerMain #zookeeper進程[txtest1 bin]$ ./zkCli.sh -server test1:2182 Connecting to test1:2182 2018-01-24 23:42:09,024 [myid:] - INFO [main:Environment100] - Client environment:zookeeper.version3.4.5-…

sqllite java 密碼,SQLite登錄檢查用戶名和密碼

我正在創建一個應用程序(使用Java和SQLite)(JFrame&#xff0c;使用Netbeans)我有我想要登錄的用戶 . (我有所有正確的包JDBC&#xff0c;SQLite等)我遇到的問題似乎是獲取用戶名/密碼來檢查我的users.db文件..我正在使用Java和SQLite . 我也在使用JDBC .我的一些代碼作為一個例…

springmvc與struts2的區別

1&#xff09;springmvc的入口是一個servlet&#xff0c;即前端控制器&#xff0c;例如&#xff1a;*.action struts2入口是一個filter過慮器&#xff0c;即前端過濾器&#xff0c;例如&#xff1a;/* 2&#xff09;springmvc是基于方法開發&#xff0c;傳遞參數是通過方法形…

power designer數據流圖_鯤云公開課 | 三分鐘帶你了解數據流架構

目前&#xff0c;市場上的芯片主要包括指令集架構和數據流架構兩種實現方式。指令集架構主要包括X86架構、ARM架構、精簡指令集運算RISC-V開源架構&#xff0c;以及SIMD架構。總體來說&#xff0c;四者都屬于傳統的通用指令集架構。傳統的指令集架構采用馮諾依曼計算方式&#…

onCreate源碼分析

原文地址Android面試題-onCreate源碼都沒看過&#xff0c;怎好意思說自己做android Activity扮演了一個界面展示的角色&#xff0c;堪稱四大組件之首&#xff0c;onCreate是Activity的執行入口&#xff0c;都不知道入口到底干了嘛&#xff0c;還學什么android,所以本文會從源碼…

linux php環境搭建教程,linux php環境搭建教程

linux php環境搭建的方法&#xff1a;首先獲取相關安裝包&#xff1b;然后安裝Apache以及mysql&#xff1b;接著修改配置文件“httpd.conf”&#xff1b;最后設置環境變量和開機自啟&#xff0c;并編譯安裝PHP即可。一、獲取安裝包PHP下載地址&#xff1a;http://cn.php.net/di…