0

I have to call two APIs to get some values. Then I am able to present the results to a user. I have achieved that by making sequential calls through callbacks:

function firstApi() {
    callFirstApi(function(firstValue) {
        secondApi(firstValue);
    });
}

function secondApi(firstValue) {
    callSecondApi(function(secondValue) {
        presentResults(firstValue, secondValue);
    });
}

function presentResults(firstValue, secondValue) {
    // do something...
}

The problem that bothers me is that API calls could be asynchronous. I would like to know if there are any problems with such solution:

var firstValue = null;
var secondValue = null;

function apiResult(value) {
    if (firstValue === null) {
        firstValue = value;
        return;
    }

    secondValue = value;
    presentResults();
}

function presentResults() {
    // do something...
}

firstApiCall(apiResult);
secondApiCall(apiResult);

JavaScript is single-threaded but I am still not sure where the context switch might occur. In other words, if there is a possibility that function call would be interrupted in the middle of execution when the asynchronous call was finished (so that for instance firstValue null check would pass for both execution paths and the second value would never be set).

6
  • 1
    The requests however are not synchronous. The first result will not have returned when you make the second request in your second version. Commented Dec 31, 2016 at 18:26
  • Not fully gathering second approach. What is issue with first approach? Commented Dec 31, 2016 at 18:26
  • Take a look at github.com/es128/async-waterfall Commented Dec 31, 2016 at 18:28
  • 4
    Your answer is in the duplicate question. You don't want to use global variables in general, but you especially don't want to use global variables in an asynchronous context. Tools have been created to deal with the situation. I strongly recommend reading up on promises. Commented Dec 31, 2016 at 18:28
  • @guest271314 I know that it works and it is a preferred way of doing things but I don't find it natural. It feels like it might lead to the problem that are hard to debug (for example captured variables by closures) Commented Dec 31, 2016 at 18:29

1 Answer 1

6

This actually has nothing to do with threading (and JavaScript is not single-threaded, not even on browsers, but unless you specifically create new threads you're only dealing with one), just asynchronicity.

If I'm reading correctly, you're saying that you want to call callFirstApi, and call callSecondApi, and when both operations complete you want to call presentResults. You don't need the calls to the API to be in series (one after another), it's fine if they're in parallel (both running at the same time), you just need to wait for both of them to complete (regardless of the order of completion)

Your solution won't (as you suspected) work because the order the results come in may not match the order you asked for them, but that solution assumes they do complete in the order you called callFirstApi, callSecondApi. You need a solution that doesn't make the assumption that the first completes before the second.

This issue of composing asynchronous operations was one of the key motivations for the development of promises, which are native in ES2015 (aka ES6) and above, and available with polyfill libraries. When you need to do some async things in series (one another another), you use a promise chain; when you need to do things in parallel (they can overlap, you just want to wait until they're all done), you start them all and use Promise.all to wait for them all to complete.

Your example using promises would look like this:

Promise.all([callFirstApi(), callSecondApi()])
    .then(function(results) {
        // results[0] is the result from the first, results[1] is the result from the second
        presentResults(results[0], results[1]);
    })
    .catch(function() {
        // an error occurred in one of the calls
    });

That assumes that callFirstApi and callSecondApi return promises. If they don't, you can promise-ify them:

function promisify(f) {
    return function() {
        return new Promise(function(resolve, reject) {
            f(function(result) {
                if (/*...result is an error...*/) {
                    reject(/*...error here...*/);
                } else {
                    resolve(/*...result here...*/);
                }
        });
    }
}

(That's simplistic, but you get the idea.)

Then:

// Once:
var firstApiWithPromises = promisify(callFirstApi);
var secondApiWithPromises = promisify(callSecondApi);

// As needed:
Promise.all([firstApiWithPromises(), secondApiWithPromises()])
    .then(function(results) {
        presentResults(results[0], results[1]);
    })
    .catch(function() {
        // an error occurred in one of the calls
    });

It doesn't matter what order they complete in, the above will wait for both before continuing, and will give you the results in order in the results array.

Sign up to request clarification or add additional context in comments.

2 Comments

Perfect, that's the explanation I wanted to get.
@T.J.Crowder you captured the actual thing I was asking for. I don't care what is the order of api calls since they are independent. In this scenario I wouldn't even mind if results array didn't match the order of promises. I had to solve this problem quickly so I came up with my first solution but I knew that I was missing something about JS what would make my life easier in the future. And indeed, you showed me how to make independent asynchronous calls.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.