Have you used...
async
/await
in C#?yield return
in C#?Promise
in JS?function*
generators in JS?Worker
, but that's for another time.)AKA: the problem.
The standard way of dealing with any JS event
// Fire doSomething() when the user clicks the element
element.addEventListener('click', doSomething);
Here the asynchronous thing is the user.
Thanks to JS being single threaded (sort of) doSomething()
is going to finish
before any other user initiated event can fire.
So far, so good. Almost all JS developers will have used this pattern.
A lot of events aren't due to user input (or maybe they just start with user input)
Often we want to do on asynchronous action after completing another - a chain.
Or we want to apply logic: if asynchronous check is true then do something different - a branch.
Error handling is essentially another branch.
AJAX/HTTP-request collection of items from server
Cache results locally in IndexedDB
IndexedDB
for a local copy.IndexedDB
IndexedDB put
IndexedDB get
You can see code that does this at https://www.evolutionjobs.com/uk/media/async-programming-in-js-76719
This nested soup of callbacks is called: Callback Hell
Promise
An object that represents an asynchronous action.
Promises have been around as an idea for a long time, and have a lot of similar concepts:
.then()
continuation.Promise
is the New API StandardMost promises will be returned by APIs - you'll be consuming them.
All new APIs use them.
Promise
: Example// Use a promise where you would have a callback
function doThingPromise(input) {
// Init a promise with a function that has two function parameters
return new Promise(function (resolve, reject) {
// Wrap a callback
doThingCallback(input, function (result) {
resolve(result);
}, function (err) {
reject(err);
} )
});
}
Promise
: Golden RuleWhen you make a promise, you always follow through.
Just like real life!
Promise
This means you always resolve or reject the Promise
.
If there is a path through the Promise
that doesn't resolve or reject then that's
a fire-n-forget bug.
Keep promises small and low level.
Promise
Let's look at 3 implementations:
ajaxPromise: XMLHttpRequest wrapper
chrome-extension-async: Chrome's extension API
idb: IndexedDB
Promise
.then([onResolved], [onRejected])
fires when the Promise
completes.
If resolve
then onResolved
is called.
If reject
then onRejected
is called.
If neither then .then()
never fires!
Promise
: Chains.then()
also returns a Promise
onResolved
can be:
undefined
- onRejected
is still checked.// JSON parse the promise result and then log it
doThingPromise(input).then(JSON.parse).then(console.log);
Promise
- continue the chain:
// Fetch a URL and pass the result to our Promise
fetch(url).then(doThingPromise).then(JSON.parse).then(console.log);
Promise
Chains are a much better way of dealing with sequences of callbacks.
Exceptions are also chained, and can be easily intercepted or caught right at the end
// logException gets called if any of the chain rejects
fetch(url).then(doThingPromise).then(JSON.parse).catch(logException);
Promise
is not always betterPromises work best where there is a single asynchronous thing to wait for.
Promises only resolve once.
Ask a question, wait, get an answer.
Promise.race()
What wins?
// Race
var tortoise = slowPromise();
var hare = fastPromise();
var dnf = rejectPromise();
var whoWins = Promise.race(tortoise, hare, dnf);
It depends on whether dnf
is quicker than hare
.
Think of it like motorsport: if anyone crashes there's a red flag and the race stops.
function*
that returns multiple values with yield
Generators are syntactic sugar - they generate a state machine so you don't have to.
Generators can be consumed as iterators:
// Out only generator function
function* getThings(start) {
yield start * 2;
yield start * 3;
return start * 4;
}
Pull consumed with:
let generator = getThings(1);
let item = generator.next();
while(!item.done) {
console.log(item.value);
item = generator.next();
}
// For-of
for(let x of getThings(1))
console.log(x);
// Spread operator
const arrayOfAll = [...getThings(1)];
// Access the iterator function
let myObj = {};
myObj[Symbol.iterator] = function* () { /* custom iterator */ };
let nativeIterator = getThings(1)[Symbol.iterator];
// Early examples may refer to this as @@iterator, now deprecated.
Generators can also have values pushed to them with yield
also passing back a value:
// In/out generator function
function* keepGettingThings(start) {
let keepGoing = true;
while (keepGoing)
// After the code yielded to has called .next
keepGoing = yield ++start;
return start;
}
Push consumed with:
let generator = getThings(3);
let item = generator.next(true);
while (!item.done) {
console.log(item.value);
// Pass back true until we get to 10
item = generator.next(item.value < 10);
}
This isn't asynchronous code, we're just passing the active thread back and forth.
async
& await
The future.
Except for Node.js, TypeScript, Babel, Chrome and (most recently) FireFox, where it is already here.
async
/await
vs Promise
async
/await
is syntactic sugar for a Promise
.
async
says: This function will return a Promise
.
await
says: return a new Promise
here,
and put everything after this in a .then()
.
// With async/await
async function doSomethingAsync(input) {
const url = BuildUrl(input);
const response = await fetch(url);
if(!response.ok)
throw new Error(response.statusText);
return response.json();
}
// Without
function doSomethingPromised(input) {
var url = BuildUrl(input);
return fetch(url).then(function(response) {
if(!response.ok)
throw new Error(response.statusText);
return response.json();
});
}
Almost! You can think of it this way, but it's using a generator.
// With async/await
async function doSomethingAsync(input) {
const url = BuildUrl(input);
const response = await fetch(url);
if(!response.ok)
throw new Error(response.statusText);
return await response.json();
}
// With generator, looks similar
function* doSomethingGenerated(input) {
var url = BuildUrl(input);
var response = yield fetch(url);
if(!response.ok)
throw new Error(response.statusText);
return response.json();
}
// Pseudo-code consumer for generator
var generator = doSomethingGenerated(input);
var promiseResponse = generator.next().value;
promiseResponse.then(function(result) {
var promiseJson = generator.next(result).value;
promiseJson.then(function(result) {
// Resolve the outer promise
resolve(result);
},
generator.error)
},
generator.error);
Not supported yet, but what should this return?
async function* both() { ...
Synchronous | Asynchronous | |
---|---|---|
Singular | function |
Promise /async function |
Plural | Iterator function* |
Observable
async function*
|
Asynchronous iterators and/or/vs Observable are rival implementations in what will become ES2018, maybe.
See also Reactive Extensions RxJS
async
Once you go async
you never go back - functions that call async
code tend to need to be async
too.
When a function calls an async
then it can:
async
too and call the nested function with await
.Promise
on up to the caller (in which case the caller now needs to be async
too).try-catch
await
causes any chained exception to be caught:
async function getThing() {
try {
const thing = await ajaxThing();
if(thing.foo)
thing.bar = await ajaxOtherThing();
// Without an await this call is fire-n-forget,
// uncomment the await to include it in the try-catch
/*await*/ saveInLocalCache(thing);
return thing;
}
catch(err) {
// Any error thrown by any await
}
}
async
in Synchronous CodeIf you don't await
an async
function then you get a Promise
:
async function getThing() {
const thing = await ajaxThing();
return thing;
}
// Then
let t = await getThing(); // t will be the thing, once getThing() has finished
let p = getThing(); // p will immediately be a promise, that resolves with the
// thing once getThing() has finished
// Either of these will get us the thing
p.then(t => /* We have t */);
t = await p;
What does this do?
// Await loop
const result = [];
for(let id of idArray)
result.push(await getThing(id));
return result;
Next doesn't start until previous has resolved.
// Fix with await at end
for(let id of idArray)
result.push(getThing(id));
return await Promise.all(result);
Start all the promises in parallel, then await
once at the end.
.NET has supported async
/await
since C#5.
Task<T>
instead of Promise
yield return
instead of yield
(Though iterators work quite differently in .NET)
.NET can block: wait synchronously for an asynchronous operation.
// Call Result to stop and wait for the task to complete
var task = GetSomethingAsync();
var block = task.Result;
However, in .NET this is almost always a bad idea.
It's bad for performance and prone to thread deadlock bugs, expect to need task.ConfigureAwait(false)
lots.
async
/await
support is coming in other languages too.
3.2 added generators with yield from
.
3.2 also added futures, which are similar to Promise
or Task
3.5 brought full support for async
/await
.
PHP 5.5 supports generators and yield
.
The PHP Asynchronous Group has written a promise implementation.
PHP is a long way from supporting async
/await
.
Java has a lot of good libraries and is very powerful, but it's extremely slow to develop new language features (typically 5-10 years behind C#).
Java 8 has CompletableFuture
, which is similar to Promise
or Task
.
No support for generators or yield
.
async
/await
support is a very long way away.
Promise
is Betterasync
/await
Makes Using Promise
EasyCallbacks are painful
Promise
is better
async
/await
makes using Promise
easy