0

I am new to node async/sync concept. these day i am confused by the sequence of async function executions. I am kinda hard to get the benefit of the async calls of single-process node:

Here is an example:

1.Sequence of async/await

Assume an async function:
async readPathInFile(filePath) {
     //open filePath, read the content and return 
     return file path in filePath
}

We have several calls like:

var a = 'a.txt';
var b = await readPathInFile(a); // call_a
var c = await readPathInFile(b); // call_b
var d = await readPathInFile(c); // call_c

As we know the readPathInFile is defined as asynchronous, but the calls call_a, call_b, call_c relies on the previous ones sequentially.

For these calls, the calls have no differences with synchronous calls.

So, What is the benefit of the asynchronous definition ?

  1. Callbacks concept has the same concerns:

For example:

readPathInFile(filePath, callback) {
     //open filePath, read the content and return 
     callback(file path in filePath)
}
var a = 'a.txt';
readPathInFile(a, (b)=>{
    //call_a
    readPathInFile(b, (c)=>{
        //call_b
        readPathInFile(c, (d)=>{
        //
        });
    });
}); // call_a
call_z(); 

//only if the call_z does not rely on a,b,c, z finishes without caring about a,b,c. This is a only benefit.

Please correct me if my assumption is wrong.

StevenWang
  • 3,625
  • 4
  • 30
  • 40
  • _"For these calls, the calls have no differences with synchronous calls"_, not exactly, there is a difference. The advantage of things being asynchronous is that when you perform `await readPathInFile(a)`, the interpreter isn't stuck/blocked on the `readPathInFile` function call. Any synchronous code outside of this async function can that you've written your code in will run while you're reading your file in the background (ie: it doesn't block). – Nick Parsons Apr 10 '22 at 11:54
  • In scripts you can use synchronous file access. It's up to 10 times faster than asynchronous file access functions. – jabaa Apr 10 '22 at 12:06
  • @NickParsons In concept, it's `unblocking`, but the code logic will cause it blocked to wait previous execution result. is it `blocking`? for a single process, one `asynchronous`, it allow other calls to proceed, but in my example, next call will not proceed before previous result comes out, otherwise, the final result will fail. Still confused. – StevenWang Apr 11 '22 at 05:43
  • @jabaa `10 times faster`, which step saves the time? – StevenWang Apr 11 '22 at 05:44
  • I don't know, which exact steps cost how much time, but I saw evaluations. Asynchronous code with event loops has much overhead. – jabaa Apr 11 '22 at 07:54

3 Answers3

1

As the term implies, asynchronous means that functions run out of order. In this respect your understanding is correct. What this implies however is not at all obvious.

To answer your question is a few words, the async/await keywords are syntax sugar, meaning they're not so much intrinsic qualities of the language, rather are shortcuts for saying something else. Specifically, the await keyword translates the expression into the equivalent Promise syntax.

function someFunction(): Promise<string> {
  return new Promise(function(resolve) {
    resolve('Hello World');
  });
}

console.log('Step 1');
console.log(await someFunction());
console.log('Step 3');

is the same as saying

...
console.log('Step 1');
someFunction().then(function (res) {
  console.log(res);
  console.log('Step 3');
});

Note the 3rd console.log is inside the then callback.

When you redefine someFunction using the async syntax, you'd get the following:

async function someFunction() {
  return 'Hello World';
}

The two are equivalent.

To answer your question

I'd like to reiterate that async/await are syntactical sugar. their effect can be replicated by existing functionality

Their purpose therefore is to combine the worlds of asynchronous and synchronous code. If you've ever seen an older, larger JS file, you'll quickly notice how many callbacks are used throughout the script. await is the mechanism to use asynchronous functions while avoiding the callback hell, but fundamentally, a function marked async will always be async regardless of how many awaits you put in front of it.

If you're struggling to understand how an await call works, just remember that the callback you pass to .then(...) can be called at any time. This is the essence of asynchronicity. await just adds a clean way to use it.

A visual example

I like analogies. Here's my favourite;

Picture yourself at a cafe. There are 3 people waiting in line. The barista takes your order and informs you that your coffee will be ready in 5 minutes. So this means you can sit down at a table and read a magazine or what ever, instead of waiting in line for the barista to make your coffee and accept payment etc.

The advantage of that is that the people behind you in the queue can get their own things done, instead of waiting in line for the coffees to be ready.

This is fundamentally how asynchronous code works. The barista can only do one thing at a time, but their time can be optimised by allowing everyone in the queue to do other things while they wait

Hope that helps

This article (linked below in the comments) has an awesome illustration: Here it is enter image description here

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
J-Cake
  • 1,526
  • 1
  • 15
  • 35
  • I can understand it a bit. `async`/`await` just fixed `call hell` However, in my code example, the `asynchronous` has no difference with `asynchronous` one in time consuming? – StevenWang Apr 11 '22 at 05:35
  • 1
    That's also deceptive. Fundamentally, JavaScript is **single threaded**, which means any amount of code, async or not will always be evaluated in series and consequently take the same amount of time. The catch is that async functions are evaluated when the engine has time available to do so. This means it can do other things in the meantime. You won't notice any difference if you're not working with IO, which is why NodeJS bothers with all this to begin with. – J-Cake Apr 11 '22 at 05:44
  • Yes, it's deceptive. In browsers, the benefit is obvious. when AJAX call begins, you can click any buttons on page to trigger other JS executions But for backend coding, if two calls begin `asynchronously`, the two lost mutual context or not `interdependent`. But my example shows the `dependence`. My understanding( guess): not every `asynchronous` is really asynchronous? – StevenWang Apr 11 '22 at 05:57
  • 1
    so in your example, that's right. The functions are asynchronous, and always will be, but since none are doing IO bound tasks or the like, there's no difference. It's a good idea to read into how the JS event loop works. I think your understanding will greatly improve. Here's the first hit on Google that seems to do a good job. I haven't read it thoroughly but it looks pretty good. https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5 – J-Cake Apr 11 '22 at 06:05
0

What is the benefit of the asynchronous definition ?

To enable standard logical-flow code to run asynchronously. Contrast your examples:

var a = 'a.txt';
var b = await readPathInFile(a); // call_a
var c = await readPathInFile(b); // call_b
var d = await readPathInFile(c); // call_c

vs.

var a = 'a.txt';
readPathInFile(a, (b)=>{
    //call_a
    readPathInFile(b, (c)=>{
        //call_b
        readPathInFile(c, (d)=>{
        //
        });
    });
}); // call_a

The latter is often called "callback hell." It's much harder to reason about even in that case, but as soon as you add branching and such, it gets extremely hard to reason about. In contrast, branching with async functions and await is just like branching in synchronous code.

Now, you could write readPathInFileSync (a synchronous version of readPathInFile), which would not be an async function and would let you do this:

var a = 'a.txt';
var b = readPathInFileSync(a); // call_a
var c = readPathInFileSync(b); // call_b
var d = readPathInFileSync(c); // call_c

So what's the difference between that and the async version at the beginning of the answer? The async version lets other things happen on the one main JavaScript thread while waiting for those I/O calls to complete. The synchronous version doesn't, it blocks the main thread, waiting for I/O to complete, preventing anything else from being handled.

Here's an example, using setTimeout to stand in for I/O:

const logElement = document.getElementById("log");
function log(msg) {
    const entry = document.createElement("pre");
    entry.textContent = Date.now() + ": " + msg;
    logElement.appendChild(entry);
    entry.scrollIntoView();
}

// Set up "other things" to happen
const things = [
    "this", "that", "the other", "something else", "yet another thing", "yada yada"
];
let starting = false; // We need this flag so we can show
                      // the "Doing X work" messsage before
                      // we actually start doing it, without
                      // giving the false impression in
                      // rare cases that other things happened
                      // during sync work
otherThing();
function otherThing() {
    if (!starting) {
        log(things[Math.floor(Math.random() * things.length)]);
    }
    setTimeout(otherThing, 250);
}

// Stand-in for a synchronous task that takes n milliseconds
function doSomethingSync(n) {
    const done = Date.now() + n;
    while (Date.now() < done) {
        // Busy-wait. NEVER DO THIS IN REAL CODE,
        // THIS IS SIMULATING BLOCKING I/O.
    }
}

// Stand-in for an asynchronous task that takes n milliseconds
function doSomething(n) {
    return new Promise(resolve => {
        setTimeout(resolve, n);
    });
}

function doWorkSync() {
    doSomethingSync(200);
    doSomethingSync(150);
    doSomethingSync(50);
    doSomethingSync(400);
}

async function doWorkAsync() {
    await doSomething(200);
    await doSomething(150);
    await doSomething(50);
    await doSomething(400);
}

// Do the work synchronously
document.getElementById("run-sync").addEventListener("click", (e) => {
    const btn = e.currentTarget;
    btn.disabled = true;
    log(">>>> Doing sync work");
    starting = true;
    setTimeout(() => {
        starting = false;
        doWorkSync();
        log("<<<< Done with sync work");
        btn.disabled = false;
    }, 50);
});

// Do the work asynchronously
document.getElementById("run-async").addEventListener("click", (e) => {
    const btn = e.currentTarget;
    btn.disabled = true;
    log(">>>> Doing async work");
    starting = true;
    setTimeout(() => {
        starting = false;
        doWorkAsync().then(() => {
            log("<<<< Done with async work");
            btn.disabled = false;
        });
    }, 50);
});
html {
    font-family: sans-serif;
}
#log {
    max-height: 5em;
    min-height: 5em;
    overflow: auto;
    border: 1px solid grey;
}
#log * {
    margin: 0;
    padding: 0;
}
<div>Things happening:</div>
<div id="log"></div>
<input type="button" id="run-sync" value="Run Sync">
<input type="button" id="run-async" value="Run Async">

There are times you don't care about that blocking, which is what the xyzSync functions in the Node.js API are for. For instance, if you're writing a shell script and you just need to do things in order, it's perfectly reasonable to write synchronous code to do that.

In constrast, if you're running a Node.js-based server of any kind, it's crucial not to allow the one main JavaScript thread to be blocked on I/O, unable to handle any other requests that are coming in.

So, in summary:

  • It's fine to write synchronous I/O code if you don't care about the JavaScript thread doing other things (for instance, a shell script).
  • If you do care about blocking the JavaScript thread, writing code with async/await makes it much easier (vs. callback functions) to avoid doing that with simple, straight-forward code following the logic of your operation rather than its temporality.
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks for your reply. I see from your reply: 1. async/await prevents: call hell 2. your unblock/block example is based on js over browser. In browsers, user actions are separated from js executions usually, like: click/ajax. It really needs unblocking feature in browsers. I see the advantage of asynchronous. But from server side, especially, from my example, it's hard to get the advantage of the blocking/unblocking methods, because it's single process. So am I right to conclude: the asynchronous benefit depends on code logic.( next one relies on previous one: no difference)? – StevenWang Apr 11 '22 at 05:24
  • Honestly, I think a bigger issue than readability is actually the size of the JS call stack. IIRC it's only in the high 100s to low 1000s in size, and for big enough projects, that may not be enough, so they introduced `async`/`await` to prevent stack overflows – J-Cake Apr 11 '22 at 06:10
  • @J-Cake - `async`/`await` is syntactic sugar over `.then`/`.catch`, so it wouldn't help in that way. (And the stack limit in modern engines is **dramatically** bigger than that, something like 30,000 frames. I remember that from testing tail-call optimization a few years back before it was abandoned.) – T.J. Crowder Apr 11 '22 at 07:20
  • Ah maybe I've got that confused with something else. I had to stresstest a recursive function recently, and it only managed a depth of ~950, thereabout. Plus AFAIK, the tail-derecursion optimisation done by v8 can actually be applied to the await syntax as well, so that it doesn't use an extra stack frame, but I have no idea where I heard that, or how reliable it is – J-Cake Apr 11 '22 at 07:29
  • @J-Cake - V8 doesn't do tail-call optimization anymore ([link](https://stackoverflow.com/a/30369729/157247)). :-) It only did so briefly, several years back. The only major JavaScript engine that does it is JavaScriptCore (in Safari). But [it looks like](https://jsfiddle.net/tjcrowder/mfg0e7pa/) the call recursion limit is only just over a third of what I thought, roughly 11k-12k. Interesting. :-) – T.J. Crowder Apr 11 '22 at 08:18
  • @StevenWang - *your unblock/block example is based on js over browser"* No, it's based on how JavaScript is specified to behave, in any environment: one main thread. The same would be true in Node.js, I'd just have to demonstrate it differently. *"But from server side, especially, from my example, it's hard to get the advantage of the blocking/unblocking methods"* I don't know what you mean by that. It's **crucial** to avoid blocking the main thread on servers, which means doing things asynchronously, not synchronously (blocking). ... – T.J. Crowder Apr 11 '22 at 08:20
  • ... *"So am I right to conclude: the asynchronous benefit depends on code logic.( next one relies on previous one: no difference)?"* No, the benefit is there for other operations as well. Suppose I want to read four things, and I don't care what order I get them in, just that I get all four of them. If I do that with synchronous blocking code, I have to read one, then the next then the next, and the main thread is blocked the whole time. In contrast, if I start all four reads asynchronously and wait for them to finish, the main thread isn't blocked during that I/O. – T.J. Crowder Apr 11 '22 at 08:22
  • @StevenWang - The one place I can see using blocking I/O calls is non-server, non-browser processes that have to do things in order (like a shell script) and don't have to do anything else at the same time. In that situation, it may make sense to use blocking I/O calls. – T.J. Crowder Apr 11 '22 at 08:23
  • 1
    @T.J.Crowder I think OP's issue is that s/he doesn't fully understand how async works – J-Cake Apr 11 '22 at 08:26
-2

Javascript Promises lets asynchronous functions return values like synchronous methods instead of immediately returning the final value the asynchronous function returns a promise to provide data in the future.

Actually Promises solves the callback hell issue.

ali t
  • 17
  • 5
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 10 '22 at 16:02
  • Promises don't solve the callback hell. They add to it. To replicate the behaviour of `await` you need to introduce more callbacks. – J-Cake Apr 11 '22 at 05:46