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.