Node.js 异步编程之 Callback介绍(一)
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.jsAPI通常提供的方式——专递一个callback作为异步回调——来实现。我们将借助三个基础模块:
1.fs:从文件ip.json读取IP列表;把结果写入到文件中;
2.request:用来发送HTTP请求,根据IP地址获取geo数据,再通过geo数据获取天气数据;
3.querystring:用来组装发送请求的url参数。
新建一个callback.js文件,引入这几个模块:
//callback.js varfs=require('fs') varrequest=require('request') varqs=require('querystring')
读取文件中的IP列表,调用fs.readFile读取文件内容,再通过JSON.parse来解析JSON数据:
... functionreadIP(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服务:
... functionip2geo(ip,callback){ varurl='http://www.telize.com/geoip/'+ip request({ url:url, json:true },function(err,resp,body){ callback(err,body) }) } ...
使用geo数据来获取weather:
... functiongeo2weather(lat,lon,callback){ varparams={ lat:lat, lon:lon, APPID:'9bf4d2b07c7ddeb780c5b32e636c679d' } varurl='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数据:
... functionips2geos(ips,callback){ vargeos=[] varip varremain=ips.length for(vari=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) } }
functiongeos2weathers(geos,callback){ varweathers=[] vargeo varremain=geos.length for(vari=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) } } ...