TheBlueSky TheBlueSky - 3 months ago 8
Javascript Question

Having trouble with promises in nodejs

I'm trying to use promises with nodejs (I'm trying with node-promise package); however, without any success. See the code below:

var express = require('express'),
request = require('request'),
promise = require('node-promise');

app.get('/promise', function(req, res) {
var length = -1;

new promise.Promise(request(
{uri: "http://www.bing.com"},
function (error, response, body) {
if (error && response.statusCode !== 200) {
console.log("An error occurred when connected to the web site");
return;
}

console.log("I'll return: " + body.length);
length = body.length;
}
)).then(function(result) {
console.log("This is what I got: " + length);
console.log("Done!");
});

res.end();
});


The output of the above code is
I'll return: 35857
only and it doesn't go to the
then
part.

I change the code then to be:

app.get('/promise', function(req, res) {
var length = -1;

promise.when(
request(
{uri: "http://www.bing.com"},
function (error, response, body) {
if (error && response.statusCode !== 200) {
console.log("An error occurred when connected to the web site");
return;
}

console.log("I'll return: " + body.length);
length = body.length;
}
),
function(result) {
console.log("This is what I got: " + length);
console.log("Done!");
},
function(error) {
console.log(error);
}
);

res.end();
});


This time the output is
This is what I got: -1
then
Done!
... looks like the "promise" was not called this time.

So:


  • What's needed to be done to fix the code above? Obviously I'm not doing it right :)

  • Is node-promise "the way to go" when I'm doing promises, or is there a better way/package? i.e. simpler and more production-ready.



Thanks.

Answer

Try jquery-deferred-for-node.

I'm not an expert but understand that this lib tends to be favoured by programmers who work both server-side and client-side.

Even if you don't already know jQuery's Deferreds, the advantages of going this route are that :

  • the documentation is excellent (it comprises links to the jQuery docs), though you may struggle to find examples specific to Node.

  • methods are chainable.

  • jQuery Callbacks are also included.

  • when one day you need to do asynchronous stuff client-side, then there's virtually nothing to relearn - the concepts are identical and the syntax very nearly so. See the "Correspondances" section in the github page hyperlinked above.

EDIT

I'm not a node.js person so I'm guessing here but based on your code above, you might want to consider something along the following lines with jquery-deferred-for-node :

var express = require('express'),
    request = require('request'),
    Deferred = require('JQDeferred');

function fetch(uri, goodCodes) {
    goodCodes = (!goodCodes) ? [200] : goodCodes;

    var dfrd = Deferred(); // A Deferred to be resolved/rejected in response to the `request()`.
    request(uri, function(error, response, body) {
        if (!error) {
            var isGood = false;

            // Loop to test response.statusCode against `goodCodes`.
            for (var i = 0; i < goodCodes.length; i++) {
                if (response.statusCode == goodCodes[i]) {
                    isGood = true;
                    break;
                }
            }

            if (isGood) {
                dfrd.resolve(response.statusCode, body);
            } else {
                dfrd.reject(response.statusCode, "An invalid response was received from " + uri);
            }
        } else {
            dfrd.reject(response.statusCode, "An error occurred attempting to connect to " + uri);
        }
    });

    // Make promise derived from dfrd available to "consumer".
    return dfrd.promise();
};

//...

app.get('/promise', function(req, resp) {
    fetch("http://www.bing.com").done(function(statusCode, result) {
        console.log("Done! This is what I got: " + result.length);
    }).fail(function(statusCode, message) {
        console.log("Error (" + statusCode + "): " + message);
    });
    resp.end();
};

Here, I have tried to write a generalized utility for fetching a resource in such a way that the asynchronous response (or error) can be handled externally. I think this is broadly along the lines of what you were trying to achieve.

Out of interest, where do console.log() messages end up with node.js?

EDIT 2

Above, I have given Deferred an initial capital, as is conventional for Constructors

With jQuery Deferreds, there must be any number of ways to fetch() consecutively. The approach below leaves fetch() as it was, and introduces fetch_() to act as its front-end. There may be simpler ways but this allows fetch() to remain a general utility, functionally equivalent to the client-side jQuery.ajax().

function fetch_(uri){
    return function(){
        return fetch(uri, [200]).then(function(statusCode, result){
            console.log("Done! This is what I got: " + result.length);
        },function(statusCode, message){
            console.log("Error (" + statusCode + "): " + message);
        });
    };
}

Note that function fetch() returns a function. It has to be like this because where fetch() is called, we want an unexecuted function, not (yet) the result of that function.

Now let's assume an array of uris is available. This can be hard-coded or built dynamically - whatever the application demands.

var uris = [
    'http://xxx.example.com',
    'http://yyy.example.com',
    'http://zzz.example.com'
];

And now, a variety of ways in which fetch_() might be called :

//v1. To call `resp.end()` when the fetching process starts.
app.get('/promise', function(req, resp) {
    fetch_(uris[0])().then(fetch_(uris[1])).then(fetch_(uris[2]));
    resp.end();
});

//v2. To call `resp.end()` when the fetching process has finished.
app.get('/promise', function(req, resp){
    fetch_(uris[0])().then(fetch_(uris[1])).then(fetch_(uris[2])).always(resp.end);
});

//v3. As v2 but building a `.then()` chain of any (unknown) length.
app.get('/promise', function(req, resp){
    var dfrd = Deferred().resolve();//
    $.each(uris, function(i, uri){
        dfrd = dfrd.then(fetch_(uri));
    });
    dfrd = dfrd.always(resp.end);
});

untested

I have more confidence in v1 and v2. v3 may work.

v2 and v3 should both give exactly the same behaviour but v3 is generalized for any number of uris.

Everything may need debugging.