193

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>

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. Commented Mar 7, 2015 at 23:39
  • 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. Commented Mar 8, 2015 at 0:00

6 Answers 6

122

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.

Sign up to request clarification or add additional context in comments.

16 Comments

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

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.

4 Comments

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

3 Comments

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
3

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.

1 Comment

"There is nothing a turing complete language can't do" is false. Ex. halting problem. related field: computability
1

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

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.