NodeJS from scratch — Part 3

Anuj Baranwal
7 min readJul 15, 2018

--

In previous parts, we discussed about —

  • basics of nodeJS
  • debugging
  • require
  • yargs
  • nodemon
  • arrow functions

Part 1 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-1-a3c1431f1e15

Part 2 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-2-3035f8f46b09

Repo — https://github.com/anuj070894/nodejs_medium_1

In this part, we will discuss about asynchronous way of doing things in nodeJS through callbacks and then by promises

It is important to understand how callstack, node apis, callback queue and event loop work together in nodeJS

Consider this program —

console.log('Starting the app'); -> (1)setTimeout(() => {
console.log('First setTimeout'); -> (2)
}, 2000);
setTimeout(() => {
console.log('Second setTimeout'); -> (3)
}, 0);
console.log('Finishing the app'); -> (4)

Let’s assume that main is the driver function here. So, when we run this program — callstack will look like —

1Main

setTimeout(2) is a node api, therefore, when (1) gets executed, setTimeout(2) is put on callstack, and then it is removed from callstack and moved to node api and callstack continues to execute the remaining stuffs.

Similarly, setTimeout(3) is put to node apis. And callstack continues to execute the remaining javascript operations.

Now, setTimeout(3)callback is put to callback queue and when event loop sees that the callstack is empty, it puts the setTimeout(3)callback in it.

And then it executes the javascript operations inside it. Similarly, setTimeout(2)callback is being executed. And then only the node process gets completed when all the things are cleared.

Callbacks in nodeJS

Callbacks are there in nodeJS to give a sense of the async behaviour so that it can execute some code after some non-blocking i/o operation has completed. To illustrate this, consider the following example —

const getUser = (id, callback) => {    const user = {        id: id
};
setTimeout(() => {callback(user);}, 2000);
}
getUser('123', (user) => { console.log(user); });

getUser is a function that accepts an id and callback as the parameter. And after 2 seconds, it executes the callback that is passed as the parameter.

Building an app(weather search)

In this part, we will be building an app where based on what address user has entered, we will show the temperature on that location. Demo command would be like below and it will show the temperature details of the location

node app.js --address='Vatika Town Ship, Surat'

Steps

  • initiate a project — npm init
  • install request , yargs
  • npm install request yargs --save

We will be using maps api of google to get the longitude and latitude details and from the address passed in as command line argument and then put this details to the https://api.darksky.net/forecast/[API_KEY]/[LATITUDE, LONGITUDE] to get the temperature details

https://maps.googleapis.com/maps/api/geocode/json?address=vatika%20town%20ship%20surat

We need to enable this apis on the respective sides. To do this, one need to generate the api key for an application in the https://console.developers.google.com/apis/library?authuser=***&folder=&organizationId=&project=***&supportedpurview=*** which can be done after creating a project successfully.

Once it is done, it is required to enable the geocode api in the library for the application. The request should include the api key and the address —

`https://maps.googleapis.com/maps/api/geocode/json?address=${encodedAddress}&key=***`

Hoping that the request to geolocation is happening successfully, we can use the longitude and latitude details to fetch the temperature from this api.

https://darksky.net/dev/docs

GET https://api.darksky.net/forecast/0123456789abcdef9876543210fedcba/42.3601,-71.0589

The body has the temperature and apparentTemperature that we want in this app.

Branch — 05_Init

in app.js

const request = require('request');const yargs = require('yargs');const geocode = require('./geocode/geocode');const forecast = require('./forecaset/forecast');const argv = yargs.options({a: {describe: 'Put your address here',string: true,alias: 'address',demand: true}}).alias('help', 'h').help().argv;geocode.getGeocodeLocation(argv.address, (errorMessage, result) => {if (errorMessage) {console.log(errorMessage);} else {console.log('Address: ', result.address);forecast.getTemperature(`https://api.darksky.net/forecast/***/${result.lat},${result.lng}`, (errorMessage, result) => {if (errorMessage) {console.log('Forecase Error has occured!!!');} else {console.log('Temperature: ', result.temperature);console.log('But it feels like: ', result.apparentTemperature);}});}});

in geocode/geocode.js

const request = require('request');const getGeocodeLocation = (address, callback) => {const encodedAddress = encodeURIComponent(address);const geocodeUrl = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodedAddress}&key=***`;request.get({url: geocodeUrl,json: true}, (error, response, body) => {if (!error && response.statusCode === 200) {callback(undefined, {address: body.results[0].formatted_address,lat: body.results[0].geometry.location.lat,lng: body.results[0].geometry.location.lng});} else {callback('Error has occured!!!');}});}module.exports = {getGeocodeLocation};

in forecase/forecast.js

const request = require('request');const getTemperature = (forecastUrl, callback) => {request.get({url: forecastUrl,json: true}, (error, response, body) => {if (error) {callback('Some error has occured!');} else {callback(undefined, {temperature: body.currently.temperature,apparentTemperature: body.currently.apparentTemperature});}}); // need to handle the case when we have to abort the request in some time.}module.exports = {getTemperature};

This is pretty much the application for weather search using command line in nodeJS and callbacks. However, as we can see, it is very difficult to manage the callbacks. At one place, we are passing the parameters with only the first parameter, and at another place we have undefined and result as the parameter. There is a better way to do this and that includes Promises.

ES6 Promises

The Promise represents that an asynchronous is finally completed and the value that is resulted from it. Without doing more theory, let’s dive straight into the implementation of promises —

var somePromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve/reject('Hey. It did/didn't worked!');
}, 2500);
});
somePromise
.then((message) => {
console.log('Success: ', message); // if resolve was called, then the first callback of then will be called
}, (errorMessage) => {
console.log('Error: ', errorMessage); // if reject was called, then the second callback of then will be called
});
one of the main difference between the promises and callbacks is that only a resolve or a reject gets called. We can't call both and expect to call both the then's success and fail methods. But in callbacks, we can call multiple callbacks from a condition.Inside the setTimeout we can only call resolve or reject indicating the success or failure of the operation and not both.

Advanced Promises

Normally, promises will require some params to be passed with. To achieve, we have to wrap our promise inside a callback that accepts the param.

var asyncAdd = (a, b) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (typeof a === 'number' && typeof b === 'number') {
resolve(a + b);
} else {
reject('Arguments must both be numbers');
}
}, 2500);
});
}
asyncAdd(5, 7).then((result) => {
console.log('Result: ', result);
}, (errorMessage) => {
console.log(errorMessage);
});

Chaining Promises

Earlier, we saw that while chaining callbacks in the app.js for our weather app, it got a bit clumsier as we have one level of branching. To avoid that, we can use chained promises, which looks like a flat way of achieving the same result and is also more clear. Before, getting into it, let’s see how chaining in promises work —

asyncAdd(4, 5) <- 2
.then((result) => {
return asyncAdd(result, 33); <- 1
}, (errorMessage) => {
console.log(errorMessage); <- 3
})
.then((result) => {
console.log(result);
}, (errorMessage) => {
console.log(errorMessage); <- 4
});
if we change the input at 1 from 33 to '33', then 4 will be called with errorMessage
however, if we change the input at 2 from 5 to '5', then it will log the errorMessage at 3. And the next then will be called. It won't call the errorMessage at 4. To handle this,
asyncAdd(4, 5) <- 2
.then((result) => {
return asyncAdd(result, 33); <- 1
})
.then((result) => {
console.log(result);
})
.catch((errorMessage) => {
console.log(errorMessage);
});
by default Promise.resolve(undefined) is returned from then success or fail callback

Axios

Axios is a library that does allow us to make requests as we have been doing with request library but it makes use of promises instead of callbacks.

To install axios —

npm install axios --save

Branch — 06_Init

in app_promises.js

const axios = require('axios');const yargs = require('yargs');const geocode = require('./geocode/geocode_promises');const forecast = require('./forecaset/forecast_promises');const argv = yargs.options({a: {describe: 'Put your address here',string: true,alias: 'address',demand: true}}).alias('help', 'h').help().argv;const encodedAddress = encodeURIComponent(argv.address);const geocodeUrl = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodedAddress}&key=***`;geocode.getGeocodeLocation(argv.address).then((result) => {const forecastUrl = `https://api.darksky.net/forecast/***/${result.lat},${result.lng}`;return forecast.getTemperature(forecastUrl);}).then((result) => {console.log('Temperature: ', result.temperature);console.log('But it feels like: ', result.apparentTemperature);}).catch((e) => {console.log(e);});

in forecast/forecast_promises.js

const axios = require('axios');const getTemperature = (forecastUrl) => {return new Promise((resolve, reject) => {axios.get(forecastUrl).then((response) => {resolve({temperature: response.data.currently.temperature,apparentTemperature: response.data.currently.apparentTemperature});}).catch((e) => {resolve(e.message);});});}module.exports = {getTemperature};

in geocode/geocode_promises.js

const axios = require('axios');const getGeocodeLocation = (address) => {return new Promise((resolve, reject) => {const encodedAddress = encodeURIComponent(address);const geocodeUrl = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodedAddress}&key=***`;axios.get(geocodeUrl).then((response) => {if (response.data.status === 'ZERO_RESULTS') {throw new Error('Not a valid address');} else {resolve({address: response.data.results[0].formatted_address,lat: response.data.results[0].geometry.location.lat,lng: response.data.results[0].geometry.location.lng});}}).catch((e) => {reject('Google Maps Api ' + e.message);});});}module.exports = {getGeocodeLocation};

NOTE: promises are better as compared to callbacks as callbacks get clumsier soon. And there will be levels of hierarchy in them. This concludes our part 3 in nodeJS.

In this part, we discussed about —

  • call stack and event loop
  • built weather search app
  • geolocation api from gmaps
  • callbacks and request module
  • promises and axios module

Part 4 — https://medium.com/@anujbaranwal/nodejs-from-scratch-part-4-d0aadf019c79

Part 5 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-5-3d3e3a84b64

Part 6.1 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-1-api-development-5dee11785d62

Part 6.2 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-2-api-development-f92f76eb3521

Part 6.3 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-3-api-development-9b046fed7364

Part 6.4 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-4-api-development-38d600a35ad9

--

--

Anuj Baranwal

Full Stack Web Developer @AdobeSystems Bangalore. Sometimes I code, I draw, I play, I edit and write