How to wait for a JavaScript Promise to resolve before resuming function?
Asked Answered
M

6

185

I'm doing some unit testing. The test framework loads a page into an iFrame and then runs assertions against that page. Before each test begins, I create a Promise which sets the iFrame's onload event to call resolve(), sets the iFrame's src, and returns the promise.

So, I can just call loadUrl(url).then(myFunc), and it will wait for the page to load before executing whatever myFunc is.

I use this sort of pattern all over the place in my tests (not just for loading URLs), primarily in order to allow changes to the DOM to happen (e.g. mimick clicking a button, and wait for divs to hide and show).

The downside to this design is that I'm constantly writing anonymous functions with a few lines of code in them. Further, while I have a work-around (QUnit's assert.async()), the test function that defines the promises completes before the promise is run.

I'm wondering if there is any way to get a value from a Promise or wait (block/sleep) until it has resolved, similar to .NET's IAsyncResult.WaitHandle.WaitOne(). I know JavaScript is single-threaded, but I'm hoping that doesn't mean that a function can't yield.

In essence, is there a way to get the following to spit out results in the correct order?

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
};

function getResultFrom(promise) {
  // todo
  return " end";
}

var promise = kickOff();
var result = getResultFrom(promise);
$("#output").append(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
Mefford answered 7/3, 2015 at 23:4 Comment(2)
if you put the append calls into a reusable function, you can then() as needed to DRY. you can also make multi-purpose handlers, steered by this to feed then() calls, like .then(fnAppend.bind(myDiv)), which can vastly cut down on anons.Titillate
What are you testing with? If it's a modern browser or you can use a tool like BabelJS to transpile your code this is certainly possible.Austerlitz
M
119

I'm wondering if there is any way to get a value from a Promise or wait (block/sleep) until it has resolved, similar to .NET's IAsyncResult.WaitHandle.WaitOne(). I know JavaScript is single-threaded, but I'm hoping that doesn't mean that a function can't yield.

The current generation of Javascript in browsers does not have a wait() or sleep() that allows other things to run. So, you simply can't do what you're asking. Instead, it has async operations that will do their thing and then call you when they're done (as you've been using promises for).

Part of this is because of Javascript's single threadedness. If the single thread is spinning, then no other Javascript can execute until that spinning thread is done. ES6 introduces yield and generators which will allow some cooperative tricks like that, but we're quite a ways from being able to use those in a wide swatch of installed browsers (they can be used in some server-side development where you control the JS engine that is being used).


Careful management of promise-based code can control the order of execution for many async operations.

I'm not sure I understand exactly what order you're trying to achieve in your code, but you could do something like this using your existing kickOff() function, and then attaching a .then() handler to it after calling it:

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
}

kickOff().then(function(result) {
    // use the result here
    $("#output").append(result);
});

This will return output in a guaranteed order - like this:

start
middle
end

Update in 2018 (three years after this answer was written):

If you either transpile your code or run your code in an environment that supports ES7 features such as async and await, you can now use await to make your code "appear" to wait for the result of a promise. It is still programming with promises. It does still not block all of Javascript, but it does allow you to write sequential operations in a friendlier syntax.

Instead of the ES6 way of doing things:

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});

You can do this:

// returns a promise
async function wrapperFunc() {
    try {
        let r1 = await someFunc();
        let r2 = await someFunc2(r1);
        // now process r2
        return someValue;     // this will be the resolved value of the returned promise
    } catch(e) {
        console.log(e);
        throw e;      // let caller know the promise was rejected with this reason
    }
}

wrapperFunc().then(result => {
    // got final result
}).catch(err => {
    // got error
});

async functions return a promise as soon as the first await is hit inside their function body so to the caller an async function is still non-blocking and the caller must still deal with a returned promise and get the result from that promise. But, inside the async function, you can write more sequential-like code using await on promises. Keep in mind that await only does something useful if you await a promise so in order to use async/await, your asynchronous operations must all be promise-based.

Missing answered 8/3, 2015 at 0:23 Comment(16)
Right, using then() is what I have been doing. I just don't like having to write function() { ... } all the time. It clutters up the code.Mefford
@dfoverdx - async coding in Javascript always involves callbacks so you always have to define a function (anonymous or named). No way around it currently.Missing
Though you can use ES6 arrow notation to make it more terse. (foo) => { ... } instead of function(foo) { ... }Bodyguard
Adding to @RobH comment quite often you can also write foo => single.expression(here) to get rid of curly and round brackets.Manatarms
@Manatarms - Yes, if you're ONLY running in browsers that support that type of ES6 syntax or if you're transpiling your code. I don't generally assume that code that runs in a browser can always make an ES6 assumption yet.Missing
@Missing - correct, either you can live a happy life of node.js developer or use transpiler or develop for ES6 friendly browsers.Manatarms
@dfoverdx - Three years after my answer, I updated it with ES7 async and await syntax as an option which is now available in node.js and in modern browsers or via transpilers.Missing
Hammer to your forehead (I had, too), that async/await is great, but essentially just syntactic sugar (great writeup on this), it allows you to write on top-level what would otherwise end up in some form of .then()–curlies. In fact, it does when being transpiled... still no way to wait in the genuine main thread of, say, a command line nodejs app to have the last line of output on a top-level.Inject
This doesn't work someValue is undefined. It's insane trying to wait for a result is so complicated.Matthaeus
Isn't async/await only pushing the uncertainty of the promise's response one function up? At the end of the day, it seems to me like he would be chaining it up but needing to deal with it in an upper level function, which wouldn't solve the problem at all.Inside
@CésarRodriguez - async/await is most useful when you want to sequence multiple asynchronous operations within a function (resulting in much simpler code). It does not change anything for the caller. The caller will still have to use a promise (either with await or .then()) if they want to know when the function has completed its asynchronous operations or what the final result of those asynchronous operations was.Missing
Thanks for the original answer and for the answer to my comment. I'm with a similar problem, but in my case it escalates to a property's getter in my class which can't be async. I'm going to try approaching it with a generator function instead. If it doesn't work for me, my next steps are Promises.all() or (god helps me) some kind of interval in the top caller waiting for the promise's answer.Inside
(If God is allowed in the callback hell)Inside
@CésarRodriguez - I'd suggest you post your own question with your own code. If your function needs to make an asynchronous call, there is NO way in Javascript to return the result of that asynchronous call as a direct return value from the function. The function will return BEFORE the asynchronous operation is complete. That's just how things work in Javascript. You will have to communicate back the asynchronous response via a callback, promise or event (or some similar mechanism). A don't see how a generator would help with that issue either.Missing
@CésarRodriguez - FYI, the canonical answer here on StackOverflow to returning an asynchronous result is this one: How to return the response from an asynchronous call.Missing
For the sake of information: I just decided to change the promise-returning fetch and use a sync XHR, which worked fine (my use case is in a background web worker).Inside
B
22

If using ES2016 you can use async and await and do something like:

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())

If using ES2015 you can use Generators. If you don't like the syntax you can abstract it away using an async utility function as explained here.

If using ES5 you'll probably want a library like Bluebird to give you more control.

Finally, if your runtime supports ES2015 already execution order may be preserved with parallelism using Fetch Injection.

Brathwaite answered 4/3, 2017 at 11:11 Comment(4)
Your generator example does not work, it is lacking a runner. Please don't recommend generators any more when you can just transpile ES8 async/await.Delicate
Thanks for your feedback. I've removed the Generator example and linked to a more comprehensive Generator tutorial with related OSS library instead.Brathwaite
This simply wraps the promise. The async function will not wait for anything when you run it, it will just return a promise. async/await is just another syntax for Promise.then.Bajaj
@Bajaj great, it returns another promise. But how you can explain when I'm chaining my code to this promise in then block – it asynchronous. When I'm simply launching code after this async/await wrapper it looks executed synchronously.Paraselene
F
13

Another option is to use Promise.all to wait for an array of promises to resolve and then act on those.

Code below shows how to wait for all the promises to resolve and then deal with the results once they are all ready (as that seemed to be the objective of the question); Also for illustrative purposes, it shows output during execution (end finishes before middle).

function append_output(suffix, value) {
  $("#output_"+suffix).append(value)
}

function kickOff() {
  let start = new Promise((resolve, reject) => {
    append_output("now", "start")
    resolve("start")
  })
  let middle = new Promise((resolve, reject) => {
    setTimeout(() => {
      append_output("now", " middle")
      resolve(" middle")
    }, 1000)
  })
  let end = new Promise((resolve, reject) => {
    append_output("now", " end")
    resolve(" end")
  })

  Promise.all([start, middle, end]).then(results => {
    results.forEach(
      result => append_output("later", result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Updated during execution: <div id="output_now"></div>
Updated after all have completed: <div id="output_later"></div>
Fastigium answered 11/5, 2018 at 22:40 Comment(3)
This won't work either. This only makes promises run in order. Anything after kickOff() is going to run before the promises finish.Matthaeus
@PhilipRego - correct, anything that you want to run afterwards needs to be in the the Promises.all then block. I've updated the code to append to the output during execution (end prints before middle - the promises can run out of order), but the then block waits for them ALL to finish and then deals with the results in order.Fastigium
BTW: You can fork this fiddle and play with the code: jsfiddle.net/akasek/4uxnkrez/12Fastigium
D
0

I've made a tiny wrapper/kernel to deal with async / non async callback dependencies in an event fashion way (and it was very interesting for my self comprehension).

Under the hood the kernel does the small-but-not-so-easy-to-understand Promise chaining logic.

It create an event object that can be use to pass an applicative context along the (masked) promise chain.

function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// your start
qk.addEventListener('my_kickoff_event', async (e) => {
    $("#output").append("start");
    await timeout(1000);
}, 'start');

// your middle
qk.addEventListener('my_kickoff_event', (e) => {
    $("#output").append(" middle");
}, 'middle', 'start'); // <<< dependency: middle needs start

// kickoff and end
qk.dispatchEvent(new QKE('my_kickoff_event')).then((e) => {
   // it's still only Promise in the end
   $("#output").append(" end");
});
// or await qk.dispatchEvent(new QKE('my_kickoff_event'));

It was not done to be the best performance-wise solution but to be easy to use across a typical app with many components that cannot easily share promises variables.

more here

try live snippet

Dakota answered 24/8, 2023 at 17:25 Comment(0)
S
0

Use await/async on promises to await them before your synchronous code:

// you must await the promises into this nested await/async
await (async () => {
  console.debug('1');
  await Promise.resolve('1');
  console.debug('2');
  await Promise.resolve('2');
})(); // /!\ do not forget the ()

// your follow-up synchronous code
console.debug('3');

which outputs 1 then 2 then 3 in expected order.

In your case,

var result;
await (async () => {
  var promise = kickOff();
  result = await promise;
  // some other promises to await if you like
})();

result = getResultFrom(result); // if getResultFrom makes sense and is synchronous
$("#output").append(result);
Secure answered 24/1, 2024 at 17:31 Comment(0)
H
0

Because of its single-threaded model, JavaScript does not provide a wait() function out of the box; instead, the async/await keywords are the best practice.

However, being curious, it is theoretically possible, though unwise, discouraged and anti-pattern, to simply wait for a Promise to resolve.

import deasync from 'deasync'

function blockForPromiseSync<T>(p: Promise<T>): T {
    let result: T | undefined = undefined;
    let error: any | undefined = undefined;

    p.then(value => { result = value })
        .catch(err => { error = err })

    deasync.loopWhile(() =>
        result === undefined && error === undefined)

    if (error !== undefined) {
        throw error!
    }
    return result!
}

async function fetchFromServer(): Promise<string> {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('hello')
        }, 2000)
    })
}

console.log('start')
const result = blockForPromiseSync(fetchFromServer())
console.log(result)
console.log('done')

prints:

start
hello
done

If we replace deasync.loopWhile with while keyword, the loop won't end because the only thread is "occupied" and there will be no chance for p to resolve.

However, the library deasync keeps the V8 engine responsive by

module.exports.loopWhile = function (pred) {
    while (pred()) {
        process._tickCallback()
        if (pred()) binding.run()
    }
}

where process._tickCallback() clears the micro-task queue and ends current cycle of event loop to allow V8 to handle other Promises, and binding.run() invokes

Napi::Value Run(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  Napi::HandleScope scope(env);
  uv_run(node::GetCurrentEventLoop(v8::Isolate::GetCurrent()), UV_RUN_ONCE);
  return env.Undefined();
}

that processes remaining events in the event queue and escapes from the while loop.

Heresiarch answered 13/4, 2024 at 23:46 Comment(1)
"There is nothing a turing complete language can't do" is false. Ex. halting problem. related field: computabilityEndor

© 2022 - 2025 — McMap. All rights reserved.