Search code examples
node.jsmultithreadingasynchronoussynchronizationthread-synchronization

How to synchronize two asynchronous calls in node?


I develop (amateur grade) in Python and Javascript (in the browser). I grew to embrace and like the asynchronous nature of JS and coupled with a reactive framework it does wonders.

I now tried NodeJS, to replace a Python script on my server. The general flow of the program is to fetch (HTTP) a few APIs and once I have all of them, to do something. This is well suited for Python, where I just make the calls serially and gather the results. Performance and timing does not matter.

While NodeJS documentation discusses blocking vs non-blocking code, it looks to me that the asynchronous nature of JavaScript in the browser is very much present in NodeJS. Specifically in my case, the port of fetch to node is based on Promises and one needs to go through hoops to make such calls blocking.

How should I synchronize my calls to finally act upon all gathered results? I have code similar to

fetch(urlOne)
  .then(res => res.json())
  .then(res => a = res.a)

fetch(urlTwo)
  .then(res => res.json())
  .then(res => b = res.b)

// here comes the moment when both a and b are to be used

I could chain one fetch with the other (in the first .then()) but this distracts from the main mechanism of the script: "get a, get b and the do something with them"). Specifically, is there something like the Python's join() which waits for the threads to be over (blocking the main thread)?


Please note that I understand and appreciate the asynchronous approach of JavaScript in the browser. It feels very natural to have an output (the rendered DOM) which is updated with some elements when they are available, asynchronously. This is also useful for a backend service such as a web server. In my case, though, the activities are very linear (or alternatively - and this is the core of my question - need to be synchronized at some point)


Solution

  • The correct way to do this is indeed with Promise.all, but there's no need for then calls with side effects (writing to variables the callback closes over). all provides the results as an array (in the same order as the calls) as its resolution value:

    Promise.all([
        fetch(urlOne)
          .then(res => res.json())
          .then(res => res.a) // <== No `a =` here
        ,
        fetch(urlTwo)
          .then(res => res.json())
          .then(res => res.b) // <== No `b =` here
    ]).then(([a, b]) => {     // <== Destructured parameter picking out the first
                              //     and second entries of the array argument
        // here comes the moment when both a and b are to be used
    });
    

    Example with a stand-in for fetch:

    // The `fetch` stand-in
    function fetch(url) {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve({
            json: () => new Promise(resolve => {
              setTimeout(() => {
                resolve({a: "a:" + url, b: "b:" + url});
              }, url === "urlOne" ? 200 : 100);
            })
          });
        }, 100);
      });
    }
    // End of stand-in
    
    Promise.all([
        fetch("urlOne")
          .then(res => res.json())
          .then(res => res.a)
        ,
        fetch("urlTwo")
          .then(res => res.json())
          .then(res => res.b)
    ]).then(([a, b]) => {
        console.log(`a = ${a}, b = ${b}`);
    });