ChatGPT解决这个技术问题 Extra ChatGPT

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

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);

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.
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.

j
jfriend00

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.


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.
@dfoverdx - async coding in Javascript always involves callbacks so you always have to define a function (anonymous or named). No way around it currently.
Though you can use ES6 arrow notation to make it more terse. (foo) => { ... } instead of function(foo) { ... }
@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.
This doesn't work someValue is undefined. It's insane trying to wait for a result is so complicated.
v
vhs

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.


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.
Thanks for your feedback. I've removed the Generator example and linked to a more comprehensive Generator tutorial with related OSS library instead.
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.
S
Stan Kurdziel

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() Updated during execution:

Updated after all have completed:


This won't work either. This only makes promises run in order. Anything after kickOff() is going to run before the promises finish.
@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.
BTW: You can fork this fiddle and play with the code: jsfiddle.net/akasek/4uxnkrez/12