Introduction to Callback for Node. js asynchronous programming (1)

  • 2020-05-19 04:12:27
  • OfStack

Node.js is based on the JavaScript engine v8 and is single-threaded. Node.js handles blocking I/O operations in an asynchronous programming manner with JavaScript on the usual Web. In Node.js, reading files, accessing databases, network requests, and so on can be asynchronous. For newcomers to Node.js or developers who have migrated from other language backgrounds to Node.js, asynchronous programming can be a painful part of the process. This chapter will introduce you to all aspects of Node.js asynchronous programming. From the most basic callback to thunk, Promise, co to async/await of the ES7 plan.

Let's start with a concrete example of asynchronous programming.

Get weather information for multiple ip locations

In the file ip.json, we have an array of several ip addresses, each with different visitors from different places, as follows:


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

It is hoped that every 1 ip can be located in the current weather. Output the results to the weather.json file as follows:

// 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" }
]

To sort out the ideas, we divided them into the following steps:

1. Read the ip address;
2. Get the geographical location of ip according to the ip address;
3. Check the local weather according to the geographical location;
4. Write the result to weather.json.

These steps are all asynchronous (read and write files can be synchronous, but for example, they are all asynchronous).

callback

First we tried to do this without using any libraries, in the way Node.js API usually provides -- sending 1 callback as an asynchronous callback. We will use three basic modules:

1.fs: read IP list from file ip. json; Write the results to a file;
2.request: used to send HTTP request, obtain geo data according to IP address, and then obtain weather data through geo data;
3.querystring: the url parameter used to assemble the sending request.

Create a new callback.js file and introduce these modules:


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

Read the IP list in the file, call fs.readFile to read the contents of the file, and then parse the JSON data through JSON.parse:


...
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)
      }
    }
  })
}
...

Then we use IP to get geo, and we use request to request an open geo service:


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

Use geo data to get 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)
  })
}
...

Now that we have got geo and weather interfaces, we still have a slightly more complicated problem to deal with. Since there are multiple ip, we need to read geo data in parallel.

...
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)
  }
}
...

Both ips2geos and geos2weathers use a relatively primitive method. remain calculates the number waiting to be returned, and remain 0 means the end of the parallel request. The processing result is loaded into an array and returned.

Finally, write the results into the weather.json file:


...
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)
}
...

By combining the above functions, we can achieve our goal:


...
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!')
              }
            })
          }
        })
      }
    })
  }
})

Haha, your mother nested, you might think this is the problem with JavaScript asynchrony, seriously, nesting is not the real problem with JavaScript asynchrony. The above code can be written as follows:


...
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)

Ok, that's all we have for callback.js. Run:


// 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" }
]
0
The weater.json file will be generated:

[
  {
    "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"
  }
]

So what's the real problem?

The problem with asynchrony, of course, is that asynchrony is essentially doing three things:

1. When the asynchronous operation ends, it needs to be notified back. Callback is a scheme;
2. The result generated by asynchrony needs to be passed back. Callback accepts one parameter of data and sends the data back.
3. What if something goes wrong with asynchrony? Callback takes one err argument and returns the error.

But do you find a lot of repetitive work (various callback)? What's wrong with the code above? Look forward to the continuation of this article.


Related articles: