Node.js 异步编程之 Callback介绍(一)


Posted in Javascript onMarch 30, 2015

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 参数,把错误传回来。

但有没有发现好多重复的工作(各种 callback)?上面的这些代码有什么问题么?请大家期待本文的续篇。

Javascript 相关文章推荐
asm.js使用示例代码
Nov 28 Javascript
javascript常见用法总结
May 22 Javascript
使用get方式提交表单在地址栏里面不显示提交信息
Feb 21 Javascript
js注册时输入合法性验证方法
Oct 21 Javascript
Vue2.0学习之详解Vue 组件及父子组件通信
Dec 12 Javascript
Angularjs中的$apply及优化使用详解
Jul 02 Javascript
jQuery轮播图实例详解
Aug 15 jQuery
bootstrap自定义样式之bootstrap实现侧边导航栏功能
Sep 10 Javascript
JS选取DOM元素常见操作方法实例分析
Dec 10 Javascript
微信小程序图表插件wx-charts用法实例详解
May 20 Javascript
JavaScript解析JSON数据示例
Jul 16 Javascript
Element-Ui组件 NavMenu 导航菜单的具体使用
Oct 24 Javascript
js动态修改表格行colspan列跨度的方法
Mar 30 #Javascript
jQuery使用hide方法隐藏页面上指定元素的方法
Mar 30 #Javascript
jquery使用hide方法隐藏指定id的元素
Mar 30 #Javascript
jQuery使用hide方法隐藏指定元素class样式用法实例
Mar 30 #Javascript
jQuery使用hide方法隐藏元素自身用法实例
Mar 30 #Javascript
javascript实现控制浏览器全屏
Mar 30 #Javascript
原生JavaScript编写俄罗斯方块
Mar 30 #Javascript
You might like
PHP读取PPT文件的方法
2015/12/10 PHP
ThinkPHP中create()方法自动验证表单信息
2017/04/28 PHP
PHP实现单例模式建立数据库连接的方法分析
2020/02/11 PHP
基于PHP的登录和注册的功能的实现
2020/08/06 PHP
Javascript面向对象编程(二) 构造函数的继承
2011/08/28 Javascript
jQuery中实现动画效果的基本操作介绍
2013/04/16 Javascript
jQuery对Select的操作大集合(收藏)
2013/12/28 Javascript
Android中的jQuery:AQuery简介
2014/05/06 Javascript
vue实现简单表格组件实例详解
2017/04/16 Javascript
VUE中的无限循环代码解析
2017/09/22 Javascript
浅谈Vue的加载顺序探讨
2017/10/25 Javascript
Vue绑定内联样式问题
2018/10/17 Javascript
在Node.js下运用MQTT协议实现即时通讯及离线推送的方法
2019/01/24 Javascript
[01:02]DOTA2上海特锦赛SHOWOPEN
2016/03/25 DOTA
Python Web框架Flask中使用百度云存储BCS实例
2015/02/08 Python
python统计文本字符串里单词出现频率的方法
2015/05/26 Python
Python数据库的连接实现方法与注意事项
2016/02/27 Python
Python使用回溯法子集树模板解决迷宫问题示例
2017/09/01 Python
itchat接口使用示例
2017/10/23 Python
解决pandas read_csv 读取中文列标题文件报错的问题
2018/06/15 Python
pandas.DataFrame选取/排除特定行的方法
2018/07/03 Python
Selenium定位元素操作示例
2018/08/10 Python
python获取交互式ssh shell的方法
2019/02/14 Python
初次部署django+gunicorn+nginx的方法步骤
2019/09/11 Python
wxpython实现按钮切换界面的方法
2019/11/19 Python
python selenium自动化测试框架搭建的方法步骤
2020/06/14 Python
PyQt5-QDateEdit的简单使用操作
2020/07/12 Python
简单的Python人脸识别系统
2020/07/14 Python
python 如何停止一个死循环的线程
2020/11/24 Python
高一生物教学反思
2014/01/17 职场文书
大学辅导员事迹材料
2014/02/05 职场文书
2014年社区学雷锋活动总结
2014/03/09 职场文书
护士求职信
2014/07/05 职场文书
三十年同学聚会感言
2015/07/30 职场文书
利用javaScript处理常用事件详解
2021/04/14 Javascript
浅谈pytorch中stack和cat的及to_tensor的坑
2021/05/20 Python