0

I've completed this exercise from learnyounode and I'm trying to refactor it using ES2015's promises (or with other libraries if it's easier). I've read about promises and I think I understand how they work, but I'd like to know if it's possible to use them in the following code and how to do it.

My goal is to make the code easier to read and understand, and also better understand promises in the process.

let http = require("http");

if (process.argv.length != 5) {
    throw new Error("Please provide the 3 URLs as a command line arguments");
}

let urls = [];
let results = [];
let count = 0;

for (let i = 2; i <  process.argv.length; i++) {
    urls.push(process.argv[i]);
}

function httpGet(index) {
    let url = urls[index];
    let result = "";

    http.get(url, res => {
        res.on("data", data => {
            result += data;
        });

        res.on('end', () => {
            count += 1;
            results[index] = result;

            if (count === 3) {
                results.forEach(function(result) {
                   console.log(result);
                });
            }
        });
    });

}

for (let i = 0; i < urls.length; i++) {
    httpGet(i);
}
Noxxys
  • 879
  • 1
  • 9
  • 19
  • 1
    Per [documentation](https://nodejs.org/api/http.html#http_http_get_options_callback) `http.get` does not return a promise and only supports the callback method. You would have to create and resolve the promise yourself. – XCS Feb 17 '17 at 10:27
  • how would bluebirds promisify help with the code above? *Answer: not at all* – Jaromanda X Feb 17 '17 at 10:36

4 Answers4

2

You could try something like this:

'use strict';

const http = require('http');
if (process.argv.length != 5) {
    throw new Error('Please provide the 3 URLs as a command line arguments');
}

let urls = [];
for (let i = 2; i <  process.argv.length; i++) {
    urls.push(process.argv[i]);
}

function httpGet(url) {
    let result = '';
    return new Promise((resolve, reject) => {
        http.get(url, function (res) {
            res.on('error', err => {
                reject(err);
            });
            res.on('data', data => {
                result += data;
            });
            res.on('end', () => {
                //You can do resolve(result) if you don't need the url.
                resolve({url, result});
            });
        })
    });
}

let promises = urls.map(url => httpGet(url));

Promise.all(promises)
    .then(results => {
        console.log(`All done. Results: ${results}`);
    })
    .catch(err => {
        console.error(err);
    });
Antonio Narkevich
  • 4,206
  • 18
  • 28
1

You could do something like this, but you should be aware that the results might not be returned, in the order of the given inputs.

Edit: It will now output data in the order given.

const http = require('http');

if (process.argv.length != 5)
    throw new Error("Please provide the 3 URLs as a command line arguments");

let urls = [];
let results = [];
let count = 0;

for (let i = 2; i < process.argv.length; i++)
    urls.push(process.argv[i]);

function httpGet (url)
{
    return new Promise((resolve, reject) => {
        let result = '';
        http.get(url, res => {
            res.on('data', data => result += data);
            res.on('end', () => resolve(result))
        }).on('err', reject);

    });
}

function printResults() {
    for (let result of results) {
        console.log(result);
    }
}

for (let i = 0; i < urls.length; i++) {
    httpGet(urls[i])
        .then(result => {
            results[i] = result;
            if (++count === 3)
                printResults();
        })
        .catch(err => console.log(err));
}
  • Thanks. It works, but indeed not preserving the order of the urls. I could preserve the order by passing the index to `httpGet(index)` and saving the result in `results[index]` as I did before. – Noxxys Feb 17 '17 at 14:52
  • @Noxxys I guess I overlooked the reason behind you passing the index to the function, I might update the answer to accommodate for it. – jonas kjellerup Feb 17 '17 at 14:57
1

While it may be useful to implement all of the promise logic yourself for educational purposes, it should be noted that there are good modules for HTTP requests with promise supports, like request-promise:

With over 1 million monthly downloads it is widely used and is a tested solution for this sort of tasks. And I always recommend using a good tested solution for any real work instead of reinventing the wheel.

Now, for educational purposes on the other hand I always recommend to reinvent as many wheels as you possibly can. For example see this answer for some examples of converting callback-based code to promises:

See also all links from this answer:

You will find a lot of examples of the same code using both callbacks and promises, so that you can examine the differences.

Community
  • 1
  • 1
rsp
  • 107,747
  • 29
  • 201
  • 177
0

The interfaces of Node's core libraries aren't promise-based. However, you can use a simple function to convert a function accepting a Node-style callback to a function returning a promise. Writing it yourself may be a good exercise. Or find it on npm. This converting function is usually named promisify. A more advanced version of it takes an object and converts all its methods.

thorn0
  • 9,362
  • 3
  • 68
  • 96
  • 3
    One moment to consider. http.get does not follow error-first convention. 'res' is a first argument. So for example bluebird.promisify will not work out of the box. – Antonio Narkevich Feb 17 '17 at 11:36