6

Firstly I am familiar with the concept of asynchronous/synchronous function. There is also a lot of questions related to mine. But I can't find my answer anywhere.

So the question is: Is there a way to return a value instead of a Promise using async/await ? As a synchronous function do.

For example:

async doStuff(param) {
  return await new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('doStuff after a while.');
      resolve('mystuffisdone'+param);
    }, 2000);
  });
}

console.log(doStuff('1'));

The only way to get the value of this function is by using the .then function.

doStuff('1').then(response => {
  console.log(response); // output: mystuffisdone1
  doOtherStuffWithMyResponse(response);
  // ...
});

Now, what I want is:

const one = doStuff('1');
console.log(one) // mystuffisdone1
const two = doStuff('2');
console.log(two) // mystuffisdone2

To explain myself, I have an asynchronous library full of callbacks. I can turn this asynchronous behavior to a synchronous behavior by using Promises and async/await to faking a synchronous behavior. But there is still a problem, it is still asynchronous in the end; outside of the scope of the async function.

doStuff('1').then((r) => {console.log(r)};
console.log('Hello wolrd');

It will result in: Hello world then mystuffisdone1. This is the expected behavior when using async/await functions. But that's not what I want.

Now my question would be: Is there a way to do the same thing as await do without the keyword async ? To make the code being synchronous ? And if not possible, why ?

Edit:

Thank you for all you answers, I think my question is not obsvious for all. To clear up what I think here is my comment to @Nikita Isaev answer.

"I understand why all I/O operations are asynchronously done; or done in parallel. But my question is more about the fact that why the engine doesn't block the caller of the sync function in an asynchronous manner ? I mean const a = doStuff(...) is a Promise. We need to call .then to get the result of this function. But why JavaScript or Node engine does not block the caller (just the block where the call is made). If this is possible, we could do const a = doStuff(...), wait and get the result in a without blocking the main thread. As async/await does, why there is no place for sync/wait ?"

Hope this is more clear now, feel free to comment or ask anything :)

Edit 2:

All precisions of the why of the answer are in the comments of the accepted answer.

Folkvir
  • 280
  • 2
  • 12
  • `async` functions naturally return a `Promise`. What you need to do is to call all your `async` functions inside another `async` function and `await` on their returned `Promise`s. – Aᴍɪʀ Jan 01 '18 at 01:24
  • btw, your `doStuff` doesn't need to be `async`, it can just return the `Promise` without `await`. Place your calls inside another `async` function and do `const one = await doStuff('1');`. – Aᴍɪʀ Jan 01 '18 at 01:27
  • 2
    you can not get a synchronous result from an asynchronous call. You say you're familiar with asynchronous/synchronous functions, yet you fail to see the impossibility of synchronous result from asynchronous call. Just a moments thought, you should realise why this is impossible – Jaromanda X Jan 01 '18 at 02:25
  • 1
    You can't make async code sync. As you can't open a package right now, that will be delivered in a week or so. You have to wait for it; in both cases. – Thomas Jan 01 '18 at 03:05
  • 1
    in `return await new Promise(...)`, the `await` is kind of pointless; like writing `return new Promise((resolve, reject) => new Promise(...).then(resolve, reject))`. You can simply write `return new Promise(...)` – Thomas Jan 01 '18 at 03:14
  • 1
    Thank you all for your anwers. I see the impossibility to do this, I just wonder why is this not possible ? Yes my mistake, the return statement would be at the end of the async function returning the value of the promise. – Folkvir Jan 02 '18 at 07:18
  • Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Heretic Monkey Apr 10 '20 at 11:18

3 Answers3

4

There are some hacky ways to do what is desired, but that would be an anti-pattern. I’ll try to explain. Callbacks is one of the core concepts in javascript. When your code launches, you may set up event listeners, timers, etc. You just tell the engine to schedule some tasks: “when A happens, do B”. This is what asynchrony is. But callbacks are ugly and difficult to debug, that’s why promises and async-await were introduced. It is important to understand that this is just a syntax sugar, your code still is asynchronous when using async-await. As there are no threads in javascript, waiting for some events to fire or some complicated operations to finish in a synchronous way would block your entire application. The UI or the server would just stop responding to any other user interactions and would keep waiting for a single event to fire.

Real world cases:

Example 1.

Let’s say we have a web UI. We have a button that downloads the latest information from the server on click. Imagine we do it synchronously. What happens?

myButton.onclick = function () {
  const data = loadSomeDataSync(); // 0
  useDataSomehow(data);
}

Everything’s synchronous, the code is flat and we are happy. But the user is not.

A javascript process can only ever execute a single line of code in a particular moment. User will not be able to click other buttons, see any animations etc, the app is stuck waiting for loadSomeDataSync() to finish. Even if this lasts 3 seconds, it’s a terrible user experience, you can neither cancel nor see the progress nor do something else.

Example 2.

We have a node.js http server which has over 1 million users. For each user, we need to execute a heavy operation that lasts 5 seconds and return the result. We can do it in a synchronous or asynchronous manner. What happens if we do it in async?

  1. User 1 connects
  2. We start execution of heavy operation for user 1
  3. User 2 connects
  4. We return data for user 1
  5. We start execution of heavy operation for user 2

I.e we do everything in parallel and asap. Now imagine we do the heavy operation in a sync manner.

  1. User 1 connects
  2. We start execution of heavy operation for user 1, everyone else is waiting for it to accomplish
  3. We return data for user 1
  4. User 2 connects

Now imagine the heavy operation takes 5 seconds to accomplish, and our server is under high load, it has over 1 million users. The last one will have to wait for nearly 5 million seconds, which is definitely not ok.

That’s why:

  1. In browser and server API, most of the i/o operations are asynchronous
  2. Developers strive to make all heavy calculation asynchronous, even React renders in an asynchronous manner.
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Kit Isaev
  • 680
  • 8
  • 19
  • Yes, This is what I understood from asynchronous functions. I can place a while loop to have the desired behavior but this is completely a bad idea to do that. I just wonder why, an asynchronous function can't be a synchronous function ? – Folkvir Jan 02 '18 at 07:25
  • 1
    I understand why all I/O operations are asynchronously done; or done in parallel. But my question is more about the fact that why the engine doesn't block the caller of the sync function in an asynchronous manner ? I mean `const a = doStuff(...)` is a Promise. We need to call `.then` to get the result of this function. But why JavaScript or Node engine does not block the caller (just the block where the call is made). If this is possible, we could do `const a = doStuff(...)` and get the result in `a` without blocking the main thread. As async/await does, why there is no place for sync/wait ? – Folkvir Jan 02 '18 at 13:40
  • @Folkvir different concepts for different intentions. `doASync(); doB()` - means “do A immediately, accomplish it, do nothing else before it accomplishes. Then do B”. `doAAsync(); doB()` - means “start doing A. Then immediately do B”. `await doAAsync(); doB();` - means “start doing a. Pause the current scope, you may do something else for now. When A accomplishes, continue execution and do B”. – Kit Isaev Jan 02 '18 at 13:49
  • Yes this exactly what I intended to do ! `await doAAsync(); doB()` But without the keyword `async`, this is not possible because we have to use it with `await`. So my question is why this is not possible ? – Folkvir Jan 02 '18 at 13:53
  • This is more a question about the concept of asynchronous and synchronous function rather than the current behavior of async/await. – Folkvir Jan 02 '18 at 13:55
  • @Folkvir in same cases, you may **not** want to block the caller. So javascript won’t do it unless it is told to. Many other languages act just as you say and have their own instruments to manage the order of execution, threads for instance. In javascript it’s asynchrony. It’s only about the language design – Kit Isaev Jan 02 '18 at 13:56
  • @Folkvir why can’t you use **await** in a non-**async** function, is that the question? – Kit Isaev Jan 02 '18 at 14:04
  • Because of this: `SyntaxError: await is only valid in async function` this is the concept of async/await. You have to use await in an async function. This why I wonder there is no way to do use await out of an async method. – Folkvir Jan 02 '18 at 14:07
  • @Folkvir this is quite a logical constraint. `async doA() { return await doB() }`: doA will be paused and its execution will be delayed, so it can only return something meaningful after a while, which makes it asynchronous as well and forces you to define it as asynchronous. If a function makes an asynchronous call and expects its result, it has to be asynchronous as well. – Kit Isaev Jan 02 '18 at 14:16
  • I understand that a function has to be asynchronous when we are doing something async. But why nobody ever had the idea to do that kind of thing: `console.log('a'); sync doStuff('b'); console.log('c');` ? This makes sense for me to have this. Await keyword emulates this into an async function. But why did not they do this at the beginning ? A colleague said to me that it's only a matter of compilation. V8 is not able to do this for the moment, i.e. detects the block where the async function was called and block this block only and not the others. Is this real or am I wrong ? – Folkvir Jan 02 '18 at 15:28
  • @Folkvir the only difference between your hypothetic `sync` keyword and the `await` keyword in ES is the fact that the letter can only be used inside of an `async` function, which definitely makes sense and I wouldn’t call it a compilation issue. By defining function as async we declare that its complete execution will take some time; that it cannot return any meaningful result right now and will only return an object called **Promise** that represents the current state of the operation. – Kit Isaev Jan 02 '18 at 15:41
  • @Folkvir calling `sync (=await) doStuff()` in a block means that the block itself has to become asynchronous, in order not to block the flow where it is called. And the root block in JS is synchronous due to the language design. – Kit Isaev Jan 02 '18 at 15:50
  • Yes thank you for your patience. I clearly understand why now this is a language design. But I stay on my opinion that it is weird to use await only with the async keyword. Especially when using Promises. Imo, It could be more understandable by just making the await behavior (ie not using async keyword) transparently. I will close the discussion here, thank you. Feel free to continue to comment if you have additionnal details. I'm curious. – Folkvir Jan 02 '18 at 16:05
  • @Folkvir `function foo() { wait bar() baz(); }`. “Bar” returns a promise, which makes it reusable. It’s up to us to decide whether to stop the current flow after calling it and wait for results, or continue execution and handle the results somewhen else. No matter if you are designing a client or a server, you will need the freedom to do the both. “Foo”, on the contrary, will **always** block the caller, which may not be the desired behavior. Moreover, blocking the caller also means blocking its caller as well, which finally leads to blocking the root code block and the entire application. – Kit Isaev Jan 02 '18 at 20:23
  • @Folkvir there **are** some cases, though, when this is acceptable. For example, when your node.js application is in the startup phase and you want to load some statics into the memory from the disc. There is no need to deal with asynchrony because you actually don’t want to do anything else, accept client connections etc., before these files are loaded. For such cases some APIs provide both sync and async methods for the same things. E.g. `readFile` accepts a callback and `readFileSync` blocks the flow and returns immediately. – Kit Isaev Jan 02 '18 at 20:28
3

No, going from promise to async/await will not get you from async code to sync code. Why? Because both are just different wrapping for the same thing. Async function returns immediately just like a promise does.

You would need to prevent the Event Loop from going to next call. Simple while(!isMyPromiseResolved){} will not work either because it will also block callback from promises so the isMyPromiseResolved flag will never be set.

BUT... There are ways to achieve what you have described without async/await. For example:

function runSync(value) {

    let isDone = false;
    let result = null;

    runAsync(value)
    .then(res => {
        result = res;
        isDone = true;
    })
    .catch(err => {
        result = err;
        isDone = true;
    })

    //magic happens here
    require('deasync').loopWhile(function(){return !isDone;});

    return result;
}

runAsync = (value) => {

    return new Promise((resolve, reject) => {

        setTimeout(() => {
            // if passed value is 1 then it is a success
            if(value == 1){
                resolve('**success**');
            }else if (value == 2){
                reject('**error**');
            }
        }, 1000);

    });

}

console.log('runSync(2): ', runSync(2));
console.log('runSync(1): ', runSync(1));

OR

  • OPTION 2: calling execFileSync('node yourScript.js') Example:
const {execFileSync} = require('child_process');
execFileSync('node',['yourScript.js']);

Both approaches will block the user thread so they should be used only for automation scripts or similar purposes.

Wesol
  • 168
  • 1
  • 9
0

Wrap the outer body in an asynchronous IIFE:

/**/(async()=>{
function doStuff(param) { // no need for this one to be async
  return new Promise((resolve, reject) => { // just return the original promise
    setTimeout(() => {
      console.log('doStuff after a while.');
      resolve('mystuffisdone'+param);
    }, 2000);
  });
}

console.log(await doStuff('1')); // and await instead of .then
/**/})().then(()=>{}).catch(e=>console.log(e))

The extra cleanup of the doStuff function isn't strictly necessary -- it works either way -- but I hope it helps clarify how async functions and Promises are related. The important part is to wrap the outer body into an async function to get the improved semantics throughout your program.

It's also not strictly necessary to have the final .then and .catch, but it's good practice. Otherwise, your errors might get swallowed, and any code ported to Node will whine about uncaught Promise rejections.

Ryan Hanekamp
  • 558
  • 4
  • 6
  • the so called "final" `.then` is never needed, strictly or not!! - but yes, the `.catch` prevents warnings in some environments – Jaromanda X Jan 01 '18 at 02:28
  • I might eventually find that to be a bad habit and stop doing it. I think I started doing it with an old Promise library I used that invoked late, only after the first .then, but that's not Promises/A+ standard behavor, so maybe I should start slapping my wrist when I get the urge – Ryan Hanekamp Jan 01 '18 at 07:04