78

How could I make the 'request' module in Node.js load things in a synchronous fashion? The best advice I've seen is to somehow use the callback to get the function to not return itself until it is done. I am trying to use the 'request' function inline in code (things need to be processed based on that data that can't be placed in callbacks).

So how could I use the callback of the 'request' module to keep it from returning itself until it is finished with loading the resource?

What I'm doing is running a loop that downloads two values from an API, and then has to do some math based on those values. While the math could be done in callbacks... the loop would advance without the values it needs to perform the next operation. (So stopping the loop from advancing until the data is ready would solve the issue)

    /* loop */ {
         /* URL Generation */


    request( {url: base + u_ext}, function( err, res, body ) {
        var split1 = body.split("\n");
        var split2 = split1[1].split(", ");
        ucomp = split2[1];
    });

    request( {url: base + v_ext}, function( err, res, body ) {
        var split1 = body.split("\n");
        var split2 = split1[1].split(", ");
        vcomp = split2[1];
    });

    /* math which needs to be after functions get variables and before loop advances */
    }
Kyle Hotchkiss
  • 10,754
  • 20
  • 56
  • 82

12 Answers12

117

The short answer is: don't. (...) You really can't. And that's a good thing

I'd like to set the record straight regarding this:

NodeJS does support Synchronous Requests. It wasn't designed to support them out of the box, but there are a few workarounds if you are keen enough, here is an example:

var request = require('sync-request'),
    res1, res2, ucomp, vcomp;

try {
    res1 = request('GET', base + u_ext);
    res2 = request('GET', base + v_ext);
    ucomp = res1.split('\n')[1].split(', ')[1];
    vcomp = res2.split('\n')[1].split(', ')[1];
    doSomething(ucomp, vcomp);

} catch (e) {}

When you pop the hood open on the 'sync-request' library you can see that this runs a synchronous child process in the background. And as is explained in the sync-request README it should be used very judiciously. This approach locks the main thread, and that is bad for performance.

However, in some cases there is little or no advantage to be gained by writing an asynchronous solution (compared to the certain harm you are doing by writing code that is harder to read).

This is the default assumption held by many of the HTTP request libraries in other languages (Python, Java, C# etc), and that philosophy can also be carried to JavaScript. A language is a tool for solving problems after all, and sometimes you may not want to use callbacks if the benefits outweigh the disadvantages.

For JavaScript purists this may rankle of heresy, but I'm a pragmatist so I can clearly see that the simplicity of using synchronous requests helps if you find yourself in some of the following scenarios:

  1. Test Automation (tests are usually synchronous by nature).

  2. Quick API mash-ups (ie hackathon, proof of concept works etc).

  3. Simple examples to help beginners (before and after).

Be warned that the code above should not be used for production. If you are going to run a proper API then use callbacks, use promises, use async/await, or whatever, but avoid synchronous code unless you want to incur a significant cost for wasted CPU time on your server.

Steven de Salas
  • 20,944
  • 9
  • 74
  • 82
  • 38
    I know this is super-stale, but I couldn't believe that nobody had +1'd your very good answer. Honestly, the thing that pisses me off most about node developers (even as I am one) is how myopic people are about how it can/should be used. Synchronous execution is a tool in the box (and sometimes an important one), just like anything else. – John Green Jun 28 '16 at 05:24
  • Thanks dude! Its a pretty new answer to an old question so maybe thats why nobody's seen it. ;) – Steven de Salas Jun 28 '16 at 05:28
  • 1
    Oh, and if I may add one more thing... I really didn't like using `sync-request` because it doesn't take the standard request-style format. This implied several issues for dealing with form-encoded posts, etc... it looks like it was only ever meant to talk to another Node server, or have very specific inputs. For instance, sending JSON encoded data isn't something I could easily handle on the back-end. Instead, I chose deasync, as displayed here: http://stackoverflow.com/questions/9884418/how-can-i-make-this-call-to-request-in-nodejs-synchronous – John Green Jun 28 '16 at 05:47
  • 6
    Thank god I found this answer. In my case I want to make a call to another server before my server crashes. I simply can't execute anything asynchronously in `process.on('uncaughtException')`, because it seems the event loop doesn't wait for it to finish, so I want my server to crash and shut down after the HTTP request is finished. – Alexandru Irimiea Apr 06 '17 at 10:51
  • Great solution for us. we send some custom js script to run on AWS env, and we merge CDN''s, custom js, user's configuration, our api calls and so on. Finally we wrap and run it by eval(), and there were problems with async calls... – lyolikaa May 31 '19 at 13:45
  • @StevendeSalas Regarding the not fit for production point , what if I am using this in an aws lambda function ? Since I don't have any other parallel things to run , isn't this a safe option ? I have a requirement where I need to add another get request to all the lambdas , and they are all written in the old callback style. Rather than changing thousands of lines of code , what if I make the get request synchronously at the beginning of the file like we do the require(). – zacurry Jun 25 '19 at 09:59
  • @zacurry, this is not really good practice but you might be able to get away with it.. the problem here is how much you will be billed for the lambda that wraps all that functionality, do the other lambdas have to be called in a specific order? Say your child lambdas take 5s, 8s and 12s to run each. If they have to be executed in order then this is going to take at least 25s, but if you can call them all at the same time then the longest your parent lambda will take is 12s. So you save half the cost. Hope this makes sense. PS Remember lambdas have a maximum running time of 5 minutes. – Steven de Salas Jun 26 '19 at 05:53
  • 2
    If synchronous code were really so bad, they would have removed fs.readFileSync and friends from the fs library in node a long time ago. These synchronous methods make certain code much simpler and make sense to use when it is appropriate. Let's not get all ideological on people! Sometimes I need the code to sit and wait for a result before it moves on. Telling the asker that their SO question is invalid is just a bad answer and should be heavily downvoted. Unbelievable. – Jared Updike Oct 09 '21 at 22:57
67

In 2018, you can program the "usual" style using async and await in Node.js.

Below is an example, that wraps request callback in a promise and then uses await to get the resolved value.

const request = require('request');

// wrap a request in an promise
function downloadPage(url) {
    return new Promise((resolve, reject) => {
        request(url, (error, response, body) => {
            if (error) reject(error);
            if (response.statusCode != 200) {
                reject('Invalid status code <' + response.statusCode + '>');
            }
            resolve(body);
        });
    });
}

// now to program the "usual" way
// all you need to do is use async functions and await
// for functions returning promises
async function myBackEndLogic() {
    try {
        const html = await downloadPage('https://microsoft.com')
        console.log('SHOULD WORK:');
        console.log(html);

        // try downloading an invalid url
        await downloadPage('http://      .com')
    } catch (error) {
        console.error('ERROR:');
        console.error(error);
    }
}

// run your async function
myBackEndLogic();
Timo
  • 5,188
  • 6
  • 35
  • 38
  • 1
    Javascript has changed a lot over the years and async/await makes code much easier to write (and less nested). Thanks for the updated response here Timo. – Kyle Hotchkiss Jul 06 '18 at 19:40
  • 9
    But this doesn't really wait for the response or error of the request before the function where it belongs proceeds... – SMPLYJR Aug 02 '19 at 01:10
  • 14
    This is not really answering the question, which is how to load data synchronously using node.js. – Steven de Salas Jan 03 '20 at 22:08
  • because it should be `await myBackEndLogic()`, but top-level awaits were not supported for a long time in Node.js and I think even now, in 2021, they have special semantics. – onetom Apr 09 '21 at 02:22
8

Though asynchronous style may be the nature of node.js and generally you should not do this, there are some times you want to do this.

I'm writing a handy script to check an API and want not to mess it up with callbacks.

Javascript cannot execute synchronous requests, but C libraries can.

https://github.com/dhruvbird/http-sync

9re
  • 2,408
  • 27
  • 27
  • 4
    *Finally* - Async is great, but callback hell awaited those who need sequential requests whose urls depended on the last response. Try making several hundred HTTP requests asynchronously! Thanks, I'll be spreading the word on this one. – Kyle Hotchkiss Jun 20 '12 at 13:55
  • 1
    Sadly this library does not npm install with newer versions of Node (14.15.5). – Jared Updike Oct 09 '21 at 23:03
5

Aredridels answer is relatively good (upvoted it), but I think it lacks the loop equivalent. This should help you:

Sync code equivalent:

while (condition) {
  var data = request(url);
  <math here>
}
return result;

Async code for serial execution:

function continueLoop() {
  if (!condition) return cb(result);
  request(url, function(err, res, body) {
    <math here>
    continueLoop()
  })
}
continueLoop()
thejh
  • 44,854
  • 16
  • 96
  • 107
4

You should take a look at library called async

and try to use async.series call for your problem.

Aleksandar Vucetic
  • 14,715
  • 9
  • 53
  • 56
3

See sync-request: https://github.com/ForbesLindesay/sync-request

Example:

var request = require('sync-request');
var res = request('GET', 'http://example.com');
console.log(res.getBody());
Jesus is Lord
  • 14,971
  • 11
  • 66
  • 97
  • This was already mentioned in much greater detail [in a previous answer](https://stackoverflow.com/a/37802984/1269037) – Dan Dascalescu Apr 04 '21 at 07:12
  • @DanDascalescu Thanks! I prefer (answers with) brevity – Jesus is Lord Apr 14 '21 at 13:17
  • The answering pattern I've been using to simultaneously get the benefits of both brevity & an involved explanation is to lead with the solution (with perhaps a _very_ brief explanation) and then follow it up with the detailed explanation. – M. Justin Mar 18 '22 at 04:41
  • Makes sense!!! Here's the detailed explanation: I think I understand what you're saying and it makes sense to me – Jesus is Lord Mar 18 '22 at 07:17
3

These days if you want to do things sequentially, you'd do something like:

for (const task of tasks) {
  const response = await request(...);
}

This code (above) runs the requests in order, but is not synchronous (meaning it does not block the main thread). If you really need synchronous, then most of these libraries boil down to something like:

const child = require('child_process');
const buffer = child.execSync(`curl https://example.com/foo.json`);
const data = JSON.parse(buffer.toString('utf8'));
bendytree
  • 13,095
  • 11
  • 75
  • 91
2

You can use retus to make cross-platform synchronous HTTP requests. It's a library based on sync-request but with a few comfort features I added:

const retus = require("retus");

const { body } = retus("https://google.com");
//=> "<!doctype html>..."

That's it!

Richie Bendall
  • 7,738
  • 4
  • 38
  • 58
  • 1
    `retus` is a wrapper around sync-request, which was mentioned in this [earlier answer](https://stackoverflow.com/questions/8775262/synchronous-requests-in-node-js/37802984#37802984). Also, you should disclose being the author of the library. – Dan Dascalescu Apr 04 '21 at 07:16
  • 1
    As you requested. – Richie Bendall Apr 04 '21 at 11:18
0

You can do something exactly similar with the request library, but this is sync using const https = require('https'); or const http = require('http');, which should come with node.

Here is an example,

const https = require('https');

const http_get1 = {
    host : 'www.googleapis.com',
    port : '443',
    path : '/youtube/v3/search?arg=1',
    method : 'GET',
    headers : {
        'Content-Type' : 'application/json'
    }
};

const http_get2 = {
host : 'www.googleapis.com',
    port : '443',
    path : '/youtube/v3/search?arg=2',
    method : 'GET',
    headers : {
        'Content-Type' : 'application/json'
    }
};

let data1 = '';
let data2 = '';

function master() {

    if(!data1)
        return;

    if(!data2)
        return;

    console.log(data1);
    console.log(data2);

}

const req1 = https.request(http_get1, (res) => {
    console.log(res.headers);

    res.on('data', (chunk) => {
        data1 += chunk;
    });

    res.on('end', () => {
        console.log('done');
        master();
    });
});


const req2 = https.request(http_get2, (res) => {
    console.log(res.headers);

    res.on('data', (chunk) => {
        data2 += chunk;
    });

    res.on('end', () => {
        console.log('done');
        master();
    });
});

req1.end();
req2.end();
user2262111
  • 563
  • 1
  • 6
  • 16
0

The easiest solution I came up for myself was to use the node's native "child_request" and simply call exec with a simple curl command inside.... all dependencies and asynchronity hassle me way too much for the simple rare cases in which I simply want to "save the http response to a variable in node"

pofqggg
  • 141
  • 2
  • 11
0

As of this writing all answers are either:

  1. Sync but with a control flow (such as using async library)
  2. Sync, but blocking [which means that all other threads on Node are stopped, which is bad performance wise], such as retus or sync-request.
  3. Out of date, such as http-request.

Check out the non-blocking sync-request, based on deasync.

Or see some of the answers in this thread, such as this, which use deasync directly.

SamGoody
  • 13,758
  • 9
  • 81
  • 91
-5

The short answer is: don't. If you want code that reads linearly, use a library like seq. But just don't expect synchronous. You really can't. And that's a good thing.

There's little or nothing that can't be put in a callback. If they depend on common variables, create a closure to contain them. What's the actual task at hand?

You'd want to have a counter, and only call the callback when the data is there:

var waiting = 2;
request( {url: base + u_ext}, function( err, res, body ) {
    var split1 = body.split("\n");
    var split2 = split1[1].split(", ");
    ucomp = split2[1];
    if(--waiting == 0) callback();
});

request( {url: base + v_ext}, function( err, res, body ) {
    var split1 = body.split("\n");
    var split2 = split1[1].split(", ");
    vcomp = split2[1];
    if(--waiting == 0) callback();
});

function callback() {
    // do math here.
}

Update 2018: node.js supports async/await keywords in recent editions, and with libraries that represent asynchronous processes as promises, you can await them. You get linear, sequential flow through your program, and other work can progress while you await. It's pretty well built and worth a try.

aredridel
  • 1,532
  • 14
  • 19
  • 2
    Running a loop that has to download two different values from an API and act upon them. The math could be done in callbacks... but than the loop would move forward without the data that was calculated in callbacks, which kinda brings a new issue up. – Kyle Hotchkiss Jan 08 '12 at 04:05
  • Edited my answer to explain one technique. – aredridel Jan 08 '12 at 05:19
  • 40
    A big pet peeve of mine is SO answers that say things like "the short answer is: don't" – tadasajon Jun 09 '17 at 18:21
  • 10
    Not a particularly helpful answer IMO... I'm validating user input and I need to return true or false based upon an API call while the CLI waits to see if it can continue. Not sure how that would ever benefit from an async call, very tiring to look up a valid question and have people lecture you on how they think you should conceptually do something rather than answer the question is just not adding value. – Bill Heller Jun 14 '17 at 07:33
  • You're going to find that there's not much _in_ node for this. It really is deeply baked into the core of its design. There's addon modules that can kinda do it, but it's never gonna be tidy and easy. – aredridel Jun 17 '17 at 19:06
  • However, now that node 8 is around: the `await` keyword is useful for doing these sorts of things with promised-based libraries like node-fetch or make-fetch-happen. – aredridel Jun 17 '17 at 19:06
  • 14
    "Don't" is not the solution to this question. F – Bryan Grace Dec 03 '17 at 20:13
  • 3
    This answer should be deleted, it's not answering the question. – Seb Jul 13 '20 at 14:39
  • Backing up and looking at the context of the question and why people ask this question is actually helpful. It's not answering the question that was asked, but it's answering with awareness of the context in which it is asked. – aredridel Jul 21 '20 at 18:26
  • 1
    "There's little or nothing that can't be put in a callback." Except complicated application logic that already works fine! It just needs one change, one synchronous HTTP request! But with async, my code ran for 4 minutes, making requests to another server running on localhost on my machine, then the async requests that still hadn't completed spent another 3 minutes getting fulfilled, after my application was ostensibly done. Async calls are not the answer to everything. – Jared Updike Oct 09 '21 at 23:08