20

I'm learning about promises and I absolutely want to make sure I am understanding their use before I continue. I am using a library for an online service which has functions which return a promise.

Nearly all examples I read either use the resolved data in chained then() functions

const result = Library.functionReturningAPromise()
result.then(function(res) {
    const obj = new Example(res)
    return obj
}).then(function(ob) {
    // do the rest of the logic within these then() functions
})

or use the resolved data within an async function

async function test() {
    const result = await Library.functionReturningAPromise()
    const obj = new Example(result)

    // do the rest of the logic
}

I want to know if there is any way at all to use the data from a resolved promise in 'normal' synchronous code

 const result = Library.functionReturningAPromise()

 // do something to resolve the promise

 const obj = new Example(result)

or if you need to always 'wrap' all your logic that uses the data from a resolved promise in an async function.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
myol
  • 8,857
  • 19
  • 82
  • 143
  • 6
    *"I want to know if there is any way at all to use the data from a resolved promise in 'normal' synchronous code "* Nope. – Felix Kling Jul 25 '18 at 12:52
  • 1
    Not possible. Doesn't matter whether you access the result through a promise or not, when it becomes available *asynchronously* then that means it is not - and cannot be - available now (synchronously). – Bergi Jul 25 '18 at 12:54
  • Writing `Library.functionReturningAPromise().then(`...etc will solve your problem. You can add as many "then" (or any of the other valid) callbacks to the promise as you like. – ADyson Jul 25 '18 at 12:57
  • 1
    As far as I understand the question, it's not about getting data from an asynchronous function, but processing the data after the asynchronous bit. The asynchronous way of `.then`-chaining and general coding style can get confusing if you're not used to it. And even if you are, once in a while everyone might get lost in one `.then` too many :) – FatalMerlin Jul 25 '18 at 13:09
  • No, but you don't technically have to wait for the promise to resolve immediately, you can call `then` or `catch` (or `async try/catch`) on the promise whenever you want, even multiple times and manipulate it in different ways. – Jake Holzinger Jul 25 '18 at 17:14

3 Answers3

14

I want to know if there is any way at all to use the data from a resolved promise in 'normal' synchronous code

There isn't a way to write completely synchronous code when handling asynchronous responses. Once any operation is asynchronous, you have to deal with the response using asynchronous techniques and cannot program with it synchronously. You must learn to program asynchronously.

The two options you show (.then() or async/await) are your two choices for handling the returned promise.

or if you need to always 'wrap' all your logic that uses the data from a resolved promise in an async function.

If you want to use await so that you can write synchronous-looking code for dealing with promises, then all that code has to be inside an async function. And, as soon as you leave the scope of that function (like want to return a value), you're returning a promise from the async function and again have to deal with the promise.

There is no way around this. It's just something one must learn in Javascript. It becomes a second nature after awhile.


As you appear to know, you can use async and await to get some synchronous-looking logic flow, but there are some things to make sure you understand when doing this. From your example:

async function test() {
    const result = await Library.functionReturningAPromise()
    const obj = new Example(result);

    // do the rest of the logic
}
  1. All functions declared with async return a promise. That's the only kind of return value you get from them. If the caller is looking for a return value or wants to know when the asynchronous operations are done or is looking for errors, they MUST use that returned promise with .then(), .catch() or again with await inside an async function.
  2. If a promise that you are awaiting rejects, it will essentially throw and abort the rest of the execution of the function and then return reject the returned promise.
  3. If you want the execution flow to abort on a rejected promise and the caller is going to handle the rejection, then that's all fine.
  4. But, if the caller isn't handling the rejection, the somebody needs to. If you're using await and you need to handle rejections locally, then you need to wrap them in try/catch (very similar syntax to synchronous exceptions).

Here's an example of using try/catch with await to handle errors locally:

async function test() {
    try {
        const result = await Library.functionReturningAPromise();
        const obj = new Example(result);

        // do the rest of the logic
    } catch(e) {
        // handle promise rejection here
    }
}

Here's an example of handling errors by the caller:

async function test() {
    const result = await Library.functionReturningAPromise();
    const obj = new Example(result);

    // do the rest of the logic
}

test().then(() => {
    console.log("all done");
}).catch(err => {
    console.log(err);
});
Malik M
  • 101
  • 1
  • 9
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Is there really no way to execute synchronous / blocking code without utilizing callbacks? – Nathanael Apr 23 '20 at 01:37
  • @NathanL - I'm not sure what you're asking because you may have the terminology confused. In an environment like node.js, there are asynchronous APIs and synchronous APIs. Some APIs like the file system API let you choose which version you want to use blocking/synchronous or non-blocking/asynchronous. If you choose the asynchronous API, there is NO way to use it without some sort of callback or promise. You can use `await` with promises to write more synchronous looking code, though it is still asynchronous. With other APIs like networking, there are only non-blocking/asynchronous APIs. – jfriend00 Apr 23 '20 at 02:11
  • @NathanL - So you cannot write purely synchronous code with asynchronous APIs in Javascript. Can't do it. It is a different way of programming. If you want blocking/synchronous networking APIs, then you need to pick a different programming environment. – jfriend00 Apr 23 '20 at 02:12
  • Sorry, to clarify: you can't run an otherwise asynchronous method in a blocking manner, such that the script doesn't continue to execute until the method is complete? – Nathanael Apr 23 '20 at 14:52
  • @NathanL - Yes, that is correct. Asynchronous, non-blocking code is non-blocking and you can't make it block and you can't make it into a synchronous operation. You can use promises to write somewhat cleaner looking and somewhat synchronous-looking code using `async/await` that still ultimately returns a promise and the caller has to use `await` or `.then()` on to get the final result. – jfriend00 Apr 23 '20 at 16:53
  • One thing to note is that it's not just that there isn't a mechanism currently for blocking until a promise completes. It is impossible to even add one in the future without huge architectural changes to current implementations of JavaScript. Without getting into the details, asynchronous JavaScript is single threaded, so if you block execution until the Promise resolves, you create deadlock because the Promise cannot resolve until execution resumes, since resolving the promise will happen on the same thread. If you want to understand this concept better, look into the JavaScript event loop. – Austin Feb 23 '21 at 14:56
  • @Austin Its not so hopeless, JS (node and browsers) have workers. All that is missing is a 'wait' keyword which knocks out the current thread, and waits for a signal from the (async) worker. Of course, the biggest thing that is missing at present is to tell a service worker to 'sleep' - if we had that we could do sync-in-async, where by default everything is async, and you run your sync 'main' method in a worker somewhere. So its really stagnation in the evolution of the model, not impossible at all. – smaudet Oct 03 '21 at 21:41
  • @smaudet Interesting. I had considered the question from the perspective of a beginner asking “why can’t I just wait for the async code to finish.” But an await-like keyword that spawns a worker thread and sleeps it until a promise finishes (or however it would really be implemented) could be really useful for situations where you need to do something asynchronous in a function parameter that expects a return value. – Austin Oct 04 '21 at 22:55
  • @smaudet But it would be dangerous if some third party library is expecting the code to run in the main thread, or if you freeze a portion of a library’s UI update loop in a browser environment. – Austin Oct 04 '21 at 23:02
  • @Austin sure, not to turn this into a discussion, but there will always be some price to pay for "progress" - even if you figured out a completely "safe" way technically to do it there is always confusing new/existing users. For nodejs we could have a module tomorrow, for the browser, without custom compiling something we would need to resort to a plugin. Technically possible but very unsupported by the major browsers... – smaudet Oct 06 '21 at 01:52
  • I'd say that this becomes a horrible practice after a while. Did not read into the above discussion. But the answer itself is disappointing. My React code has half a dozen extra useState hooks because of the menace that async code is. Since web3.js is, for the most part, asynchronous. Even when you just need to get data from the browser. – MickeyDickey Jan 05 '22 at 20:57
  • @MickeyDickey - What exactly are you objecting to? This answer just explains `.then()` and `async/await` and error handling which are your two options for dealing with promises from asynchronous operations. There's nothing here that "becomes a horrible practice" after awhile. These are good practices for handling asynchronous operations using promises. Perhaps you're still wishing (like the OP) that you can handle asynchronous operations in a synchronous fashion, but such a capability does not exist in Javascript, though async/await can simplify some code. – jfriend00 Jan 05 '22 at 22:37
  • @jfriend00, you're absolutely right with your last sentence - I am wishing for a more synchronous approach. My comment doesn't object against your examples, but rather against the convoluted mess that the code becomes when it has too many async methods. Your practice here is as good as any other, to be fair. So don't let my comment get to you, personally. I am just hating on async <-> sync functions in general. – MickeyDickey Jan 10 '22 at 16:25
  • @MickeyDickey - It's really just a matter of learning how to program with it and then it actually works pretty well, particularly with `await`. Once you get used to it, I think it's simpler to code than doing blocking/synchronous I/O and using threads for scalability. When using threads, you have all sorts of potential concurrency issues you have to program for and debug problems with when accessing any shared data that you generally don't have with the nodejs model and those issues can be quite challenging, particularly for developers that aren't super experienced. – jfriend00 Jan 11 '22 at 01:50
  • @jfriend00 You still have data race conditions with async/await... it is true that true concurrency allows for even more race conditions, but it also allows for more recovery options. If you deadlock on a single process between two threads, you're at the mercy of the OS (or browser) to save you... Plus, MickeyDickey is right, the async/await module pollutes the codebase and you cannot break free because you lack the concurrency... so it produces worse code than before. – smaudet Jan 15 '22 at 01:51
  • 1
    @smaudet - We will just have to disagree here. The opportunity for race conditions with async/await is far, far, far less than with actual threads because control NEVER flips to another thread of execution except when you explicitly use `await`. If you're hoping the OS saves you from two deadlocked threads, good luck. That's just buggy code that's crazy hard to test to find those threading race conditions. The fact that you are referring to an "async/await module" makes it sounds like don't even know what it is. It's not a module. They are first class features of the language. – jfriend00 Jan 15 '22 at 03:35
  • @smaudet - Oh, and nodejs has workerThreads if you have a real use-case for concurrent execution. But, since that isn't needed most of the time you're doing I/O, you get the simpler coding model for most code. I think we're drifting off-topic here from the original question so we probably shouldn't be debating the merits of a language feature in comments any further. – jfriend00 Jan 15 '22 at 03:46
0

If you want to "leave" the asynchronous context you could just wait for the Promise to fulfill, and then() call one of your "regular" functions as a callback.

I can understand that the asynchronous coding style can be very confusing, but effectively this is just what then(function () {}) does.

If this coding style is easier for you to understand or work with, it is functionally equivalent.

function first() {
    console.log("sync!");
    doAsync();
}

function doAsync() {
    console.log("now we're async!");
    Library.functionReturningAPromise().then(second);
}

function second() {
    console.log("sync again!");
    // ...
}
FatalMerlin
  • 1,463
  • 2
  • 14
  • 25
  • 3
    "*Just waiting for the Promise to fulfill*" does **not** leave the asynchronous context. – Bergi Jul 25 '18 at 13:40
  • @Bergi please note the quotes around `leave`. Also please take my comment on the question into account. Hope that makes it clear :) I don't mean to say that you can actually ever leave an asynchronous context, there is simply no going back. But you can "escape" the async and it's associated style (`then`, `await`, `async`) with this approach. – FatalMerlin Jul 25 '18 at 13:43
  • 1
    I would argue that this coding style is not functionally equivalent, one of the main purposes of using Promises to manage asynchronous code is the error handling pipeline. The proposed approach turns Promises into glorified callbacks. While it may be easier to understand when you're used to writing synchronous code it is worth taking the time to understand how Promises work so you can leverage their fundamental features. – Jake Holzinger Jul 25 '18 at 17:08
0

It's possible to use a combination of async/await and then() to program synchronously if you wrap their use in a function that returns a default value.

Here's an example that makes use of memoization to return a result or query it if needed:

const scope = {
  // someResult is wrapped in an object so that it can be set even if it's a primitive
  // (e.g. number / string).
  someResult: null,
};

function getResult() {
  if (scope.someResult !== null) {
    // If we already have a result, return it. No need to query Library again.
    return scope.someResult;
  }

  // Note: This example doesn't implement debouncing (avoiding repeated Library queries while
  // Library is still being queried).
  queryLibrary().then(result => {
    if (scope.someResult === null) {
      // If the Library call succeeded and we haven't already stored its result, store it.
      scope.someResult = result;
      console.log('We have a result now!', getResult());
    }
  });

  return scope.someResult;
}

async function queryLibrary() {
  const result = await functionReturningAPromise();
  // Do some stuff with result in a nice async / await way.
  result.push(9000, 1336);
  return result;
}

/** This is some complicated async function from Library. */
async function functionReturningAPromise() {
  return [419, 70];
}

// Calling getResult() will return either null or a list of numbers.
console.log('Do we have a result yet?', getResult());
Acorn1010
  • 114
  • 1
  • 5