-1

I know their are plenty of questions regarding async and await and promises on here, Ive looked at all of them and I cant seem to make any of the solutions work with my specific situation. Im sure it is because I am confused about the use of promises and callbacks. All I want to do is fire these 3 api calls in order. This is my most recent attempt :-

    async function getData() {
        var firstCall = api.getBaseInfo(name, num, (error, response) => {
            console.log(response)
        });
        var secondCall = api.getMainInfo(address, company, { type:'init' , family: name, id: num }, (error, response) => {
            console.log(response) 
        });
        var thirdCall =  api.getBackData(num, (error, orders, genre) => {
            console.log(orders)
        });
        await firstCall
        await secondCall
        await thirdCall
    }
    getData()

I have tried all sorts of variations incorporating .then, multiple async functions, normal functions but everything I do just has the output arrive as if all 3 calls are fired simultaneously. Im not bothered what methods are used, I would just like to know how I can arrange my code so that it each call will execute after the previous has finished.

New Bee
  • 31
  • 5
  • "_output arrive as if all 3 calls are fired simultaneously_" - that's simply because the console is async. Look at the output and you'll see a little blue `i` icon. – Randy Casburn Dec 10 '21 at 04:15
  • When i use await at the start of each of the api calls I get the exact same result as I do when having it set up this way - that was how I originally coded it but changed it to this format after I saw a solution on here suggesting it. But yes, obviously what you say is correct – New Bee Dec 10 '21 at 04:21
  • All you need to do is collect & return what you need from the `getData()` function and use `.then()` -> `getData().then(console.log);` – Randy Casburn Dec 10 '21 at 04:23
  • (1) Are you sure the `api.getSomething(...)` expressions return Promise (which is necessary for `await` to have any pupose)? The expressions each include a nodeback, so it seems likely that Promise is not returned (no guarantee either way). Try running a test to see what is returned with/without nodebacks. – Roamer-1888 Dec 10 '21 at 04:41
  • (2) Once you have `api.getSomething(...)` expressions that return Promise, think about the timing. Do the three `await ...` lines affect the timing of the three api calls? The answer is no, because the api calls have already been made. So to make the three api calls "execute after the previous has finished", where do the awaits belong? The response to each api call needs to be awaited, so change the form of the three calls to `var result_1 = await api.getSomethng(...);` (without nodeback), and purge the three existing `await ...Call` lines. Voila! – Roamer-1888 Dec 10 '21 at 04:41
  • (3) You will then stand a chance of running into an "unhandled exception", but that's the subject of another question. – Roamer-1888 Dec 10 '21 at 04:44
  • Voted to repoen: Converting an existing callback API to promises is (potentially) no more than part of the answer. – Roamer-1888 Dec 10 '21 at 05:07
  • @Roamer-1888 Write that as an answer – slebetman Jan 05 '22 at 07:41
  • @slebetman, not concrete enough. An answer would just get downvoted. Besides, the OP is unresponsive and I'm not prepared to put in any more effort. – Roamer-1888 Jan 06 '22 at 12:54

1 Answers1

1

From your description it appears that your functions don't return Promises. Instead they handle asynchronous behavior via callbacks (where you write your console.log). Without promises async/await does nothing at all. The reason your code does not throw any errors is that await was designed to silently ignore anything that is not a promise in order to support functions that may or may not return a Promise (eg. in some situations the function returns a Promise but in others it returns null)

In order to execute your functions in sequence you need to wait for each function to complete. And the one place we know they complete is inside their callbacks. So you need to rewrite your code as follows:

function getData() {
    api.getBaseInfo(name, num, (error, response) => {
        console.log(response);

        api.getMainInfo(address, company, { type:'init' , family: name, id: num }, (error, response) => {
            console.log(response);

            api.getBackData(num, (error, orders, genre) => {
                console.log(orders)
            });
        });
    });
}
getData();

This way each function is allowed to complete before executing the next function.

Promises

While the above code works, more complicated code will generate very deep nesting. The nesting can be solved by using named functions instead of anonymous functions. In fact in traditional programming (eg. C++) this is encouraged - you should refactor your code so that each function is as short as needed:

function baseInfoHandler(error, response) {
    console.log(response);
    const query = { type:'init' , family: name, id: num };

    api.getMainInfo(address, company, query, mainInfoHandler);
}

function mainInfoHandler(error, response) {
    console.log(response);

    api.getBackData(num, backDataHandler);
}

function backDataHandler(error, orders, genre) {
    console.log(orders);
}

function getData() {
    api.getBaseInfo(name, num, baseInfoHandler);
}
getData();

The above code flattens the nesting of callbacks. Some people (especially old-timers) might argue that this code is better written than the one with anonymous functions. However there are downsides to doing this refactor.

The first downside is a very minor one and can basically be ignored: some people just don't like to declare additional functions or variables. Using anonymous functions means we don't need to think about function names. But this is just a matter of taste and like I mentioned can be completely ignored.

The second downside is more critical. Flattening the code like the above will not work if your code use closures. Or at least it is more difficult to implement if you depend on closures. As such we need a way to nest scopes (maintain closures) but still avoid the deep nesting like the first example I gave.

Along comes a design pattern. A bunch of talented programmers started discussing on various forums and blog posts about encapsulating asynchronous callbacks in an object that you can return. The caller of the function can then use said object to handle the asynchronous behavior in any way they like. The name of this design pattern is called Promise and before javascript shipped with a default implementation of Promise there were several libraries that implemented this design pattern often with different features but are all compatible with each other in one way: they all have a .then() method that you can pass your callbacks to.

Note: this is all the Promise design pattern does. Instead of you writing a function that accepts a callback you write a function that returns a Promise. The Promise has a .then() method that accepts the callback.

The code is still callback based but now we can make the code neater by flattening the callback nesting. First we need to convert your functions to promises:

function promiseBaseInfo (name,num) {
    return new Promise((resolve, reject) => {
        api.getBaseInfo(name, num, (error, response) => {
            if (error) {
                reject(error);
            }
            else {
                resolve(response);
            }
        });
    });
}

function promiseMainInfo(address, company, query) {
    return new Promise((resolve, reject) => {
        api.getMainInfo(address, company, query, (error, response) => {
            if (error) {
                reject(error);
            }
            else {
                resolve(response);
            }
        });
    });
}

function promiseBackData(num) {
    return new Promise((resolve, reject) => {
        api.getBackData(num, (error, orders, genre) => {
            if (error) {
                reject(error);
            }
            else {
                resolve({
                    orders: orders,
                    genre: genre
                });
            }
        });
    });
}

Now that we have the Promisified versions of your functions we can use them like this:

function getData() {
    promiseBaseInfo(name, num)
        .then(response => {
            console.log(response);

            return promiseMainInfo(address, company, { type:'init' , family: name, id: num });
        })
        .then(response => {
            console.log(response);

            return promiseBackData(num);
        })
        .then(response => {
            console.log(response.orders);
        });
}
getData();

As you can see, the .then() are all at the same level of nesting.

async/await

While Promises help a lot in simplifying asynchronous code they are still callback based. Additionally, doing things like looping through a bunch of asynchronous tasks are complicated because you need to write a kind of recursive function to do so (which puts us back in callback territory).

ECMAScript version 6 introduced a new syntax to handle promises: the async and await keywords.

What it does is simply compile code written like this:

async function foo () {
    let x = await bar();
    console.log(x);
}

into this:

function foo () {
    return bar().then(x => console.log(x));
}

Internally from the interpreter's point of view nothing new was added to Javascript. These are still Promises. However the compiler now handles how to structure your code correctly into promises.

Using async/await we can further simplify your code to:

async function getData() {
    let response1 = await promiseBaseInfo(name, num);
    console.log(response1);

    let response2 = await promiseMainInfo(address, company, { type:'init' , family: name, id: num });
    console.log(response2);

    let response3 = await promiseBackData(num);
    console.log(response3.orders);
}
getData();

Note that neither Promise nor async/await are necessary to make your code work the way you want. And they don't change the behavior of your code - the getData() function is still asynchronous (not synchronous) and will return before it logs all the responses. It just makes the code easier to read. However, behind the scenes there is still a callback being scheduled and executed at a later date only with async/await we let the compiler write the callbacks and we just write the code in linear style. It is important to remember that even with async/await the code is still not synchronous.

Also note that we still cannot use your original api... functions. We need to convert them to their Promisified versions.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • Thanks very much, this helped a great deal. Apologies for not responding earlier but my question got closed and I hadnt realised it was reopened – New Bee Jan 09 '22 at 20:10