Related
I read that async functions marked by the async keyword implicitly return a promise:
async function getVal(){
return await doSomethingAync();
}
var ret = getVal();
console.log(ret);
but that is not coherent...assuming doSomethingAsync() returns a promise, and the await keyword will return the value from the promise, not the promise itsef, then my getVal function should return that value, not an implicit promise.
So what exactly is the case? Do functions marked by the async keyword implicitly return promises or do we control what they return?
Perhaps if we don't explicitly return something, then they implicitly return a promise...?
To be more clear, there is a difference between the above and
function doSomethingAync(charlie) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(charlie || 'yikes');
}, 100);
})
}
async function getVal(){
var val = await doSomethingAync(); // val is not a promise
console.log(val); // logs 'yikes' or whatever
return val; // but this returns a promise
}
var ret = getVal();
console.log(ret); //logs a promise
In my synopsis the behavior is indeed inconsistent with traditional return statements. It appears that when you explicitly return a non-promise value from an async function, it will force wrap it in a promise.
I don't have a big problem with it, but it does defy normal JS.
The return value will always be a promise. If you don't explicitly return a promise, the value you return will automatically be wrapped in a promise.
async function increment(num) {
return num + 1;
}
// Even though you returned a number, the value is
// automatically wrapped in a promise, so we call
// `then` on it to access the returned value.
//
// Logs: 4
increment(3).then(num => console.log(num));
Same thing even if there's no return! (Promise { undefined } is returned)
async function increment(num) {}
Same thing even if there's an await.
function defer(callback) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(callback());
}, 1000);
});
}
async function incrementTwice(num) {
const numPlus1 = await defer(() => num + 1);
return numPlus1 + 1;
}
// Logs: 5
incrementTwice(3).then(num => console.log(num));
Promises auto-unwrap, so if you do return a promise for a value from within an async function, you will receive a promise for the value (not a promise for a promise for the value).
function defer(callback) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(callback());
}, 1000);
});
}
async function increment(num) {
// It doesn't matter whether you put an `await` here.
return defer(() => num + 1);
}
// Logs: 4
increment(3).then(num => console.log(num));
In my synopsis the behavior is indeed inconsistent with traditional
return statements. It appears that when you explicitly return a
non-promise value from an async function, it will force wrap it in a
promise. I don't have a big problem with it, but it does defy normal
JS.
ES6 has functions which don't return exactly the same value as the return. These functions are called generators.
function* foo() {
return 'test';
}
// Logs an object.
console.log(foo());
// Logs 'test'.
console.log(foo().next().value);
Yes, an async function will always return a promise.
According to the tc39 spec, an async function desugars to a generator which yields Promises.
Specifically:
async function <name>?<argumentlist><body>
Desugars to:
function <name>?<argumentlist>{ return spawn(function*() <body>, this); }
Where spawn "is a call to the following algorithm":
function spawn(genF, self) {
return new Promise(function(resolve, reject) {
var gen = genF.call(self);
function step(nextF) {
var next;
try {
next = nextF();
} catch(e) {
// finished with failure, reject the promise
reject(e);
return;
}
if(next.done) {
// finished with success, resolve the promise
resolve(next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
Your question is: If I create an async function should it return a promise or not? Answer: just do whatever you want and Javascript will fix it for you.
Suppose doSomethingAsync is a function that returns a promise. Then
async function getVal(){
return await doSomethingAsync();
}
is exactly the same as
async function getVal(){
return doSomethingAsync();
}
You probably are thinking "WTF, how can these be the same?" and you are right. The async will magically wrap a value with a Promise if necessary.
Even stranger, the doSomethingAsync can be written to sometimes return a promise and sometimes NOT return a promise. Still both functions are exactly the same, because the await is also magic. It will unwrap a Promise if necessary but it will have no effect on things that are not Promises.
Just add await before your function when you call it :
var ret = await getVal();
console.log(ret);
async doesn't return the promise, the await keyword awaits the resolution of the promise. async is an enhanced generator function and await works a bit like yield
I think the syntax (I am not 100% sure) is
async function* getVal() {...}
ES2016 generator functions work a bit like this. I have made a database handler based in top of tedious which you program like this
db.exec(function*(connection) {
if (params.passwd1 === '') {
let sql = 'UPDATE People SET UserName = #username WHERE ClinicianID = #clinicianid';
let request = connection.request(sql);
request.addParameter('username',db.TYPES.VarChar,params.username);
request.addParameter('clinicianid',db.TYPES.Int,uid);
yield connection.execSql();
} else {
if (!/^\S{4,}$/.test(params.passwd1)) {
response.end(JSON.stringify(
{status: false, passwd1: false,passwd2: true}
));
return;
}
let request = connection.request('SetPassword');
request.addParameter('userID',db.TYPES.Int,uid);
request.addParameter('username',db.TYPES.NVarChar,params.username);
request.addParameter('password',db.TYPES.VarChar,params.passwd1);
yield connection.callProcedure();
}
response.end(JSON.stringify({status: true}));
}).catch(err => {
logger('database',err.message);
response.end(JSON.stringify({status: false,passwd1: false,passwd2: false}));
});
Notice how I just program it like normal synchronous particularly at
yield connection.execSql and at yield connection.callProcedure
The db.exec function is a fairly typical Promise based generator
exec(generator) {
var self = this;
var it;
return new Promise((accept,reject) => {
var myConnection;
var onResult = lastPromiseResult => {
var obj = it.next(lastPromiseResult);
if (!obj.done) {
obj.value.then(onResult,reject);
} else {
if (myConnection) {
myConnection.release();
}
accept(obj.value);
}
};
self._connection().then(connection => {
myConnection = connection;
it = generator(connection); //This passes it into the generator
onResult(); //starts the generator
}).catch(error => {
reject(error);
});
});
}
I have an async generator function that I would like to just get the return value for without calling next a bunch of times. Right now I have something like this:
async function *getData(): AsyncGenerator<object[], object[]>
{
let results = []
let res = {};
while (true)
{
res = await apiCall(res.next);
results.push(...res.data)
if (!res.next) break;
yield res.data;
}
return results;
}
async function main()
{
const genFn = getData();
while (!(await genFn.next()).done) {}
}
Is there a better/more concise way that doesn't need the while loop?
There is not much you can do besides creating a function that does it for you to clean things up. You can use a for-await instead of a while loop though.
async function drainAsyncIterable(iterable) {
const iterator = iterable[Symbol.asyncIterator]();
while (!(await iterator.next()).done) {}
}
vs
async function drainAsyncIterable(iterable) {
for await (const _ of iterable) {}
}
I read that async functions marked by the async keyword implicitly return a promise:
async function getVal(){
return await doSomethingAync();
}
var ret = getVal();
console.log(ret);
but that is not coherent...assuming doSomethingAsync() returns a promise, and the await keyword will return the value from the promise, not the promise itsef, then my getVal function should return that value, not an implicit promise.
So what exactly is the case? Do functions marked by the async keyword implicitly return promises or do we control what they return?
Perhaps if we don't explicitly return something, then they implicitly return a promise...?
To be more clear, there is a difference between the above and
function doSomethingAync(charlie) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(charlie || 'yikes');
}, 100);
})
}
async function getVal(){
var val = await doSomethingAync(); // val is not a promise
console.log(val); // logs 'yikes' or whatever
return val; // but this returns a promise
}
var ret = getVal();
console.log(ret); //logs a promise
In my synopsis the behavior is indeed inconsistent with traditional return statements. It appears that when you explicitly return a non-promise value from an async function, it will force wrap it in a promise.
I don't have a big problem with it, but it does defy normal JS.
The return value will always be a promise. If you don't explicitly return a promise, the value you return will automatically be wrapped in a promise.
async function increment(num) {
return num + 1;
}
// Even though you returned a number, the value is
// automatically wrapped in a promise, so we call
// `then` on it to access the returned value.
//
// Logs: 4
increment(3).then(num => console.log(num));
Same thing even if there's no return! (Promise { undefined } is returned)
async function increment(num) {}
Same thing even if there's an await.
function defer(callback) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(callback());
}, 1000);
});
}
async function incrementTwice(num) {
const numPlus1 = await defer(() => num + 1);
return numPlus1 + 1;
}
// Logs: 5
incrementTwice(3).then(num => console.log(num));
Promises auto-unwrap, so if you do return a promise for a value from within an async function, you will receive a promise for the value (not a promise for a promise for the value).
function defer(callback) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(callback());
}, 1000);
});
}
async function increment(num) {
// It doesn't matter whether you put an `await` here.
return defer(() => num + 1);
}
// Logs: 4
increment(3).then(num => console.log(num));
In my synopsis the behavior is indeed inconsistent with traditional
return statements. It appears that when you explicitly return a
non-promise value from an async function, it will force wrap it in a
promise. I don't have a big problem with it, but it does defy normal
JS.
ES6 has functions which don't return exactly the same value as the return. These functions are called generators.
function* foo() {
return 'test';
}
// Logs an object.
console.log(foo());
// Logs 'test'.
console.log(foo().next().value);
Yes, an async function will always return a promise.
According to the tc39 spec, an async function desugars to a generator which yields Promises.
Specifically:
async function <name>?<argumentlist><body>
Desugars to:
function <name>?<argumentlist>{ return spawn(function*() <body>, this); }
Where spawn "is a call to the following algorithm":
function spawn(genF, self) {
return new Promise(function(resolve, reject) {
var gen = genF.call(self);
function step(nextF) {
var next;
try {
next = nextF();
} catch(e) {
// finished with failure, reject the promise
reject(e);
return;
}
if(next.done) {
// finished with success, resolve the promise
resolve(next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
Your question is: If I create an async function should it return a promise or not? Answer: just do whatever you want and Javascript will fix it for you.
Suppose doSomethingAsync is a function that returns a promise. Then
async function getVal(){
return await doSomethingAsync();
}
is exactly the same as
async function getVal(){
return doSomethingAsync();
}
You probably are thinking "WTF, how can these be the same?" and you are right. The async will magically wrap a value with a Promise if necessary.
Even stranger, the doSomethingAsync can be written to sometimes return a promise and sometimes NOT return a promise. Still both functions are exactly the same, because the await is also magic. It will unwrap a Promise if necessary but it will have no effect on things that are not Promises.
Just add await before your function when you call it :
var ret = await getVal();
console.log(ret);
async doesn't return the promise, the await keyword awaits the resolution of the promise. async is an enhanced generator function and await works a bit like yield
I think the syntax (I am not 100% sure) is
async function* getVal() {...}
ES2016 generator functions work a bit like this. I have made a database handler based in top of tedious which you program like this
db.exec(function*(connection) {
if (params.passwd1 === '') {
let sql = 'UPDATE People SET UserName = #username WHERE ClinicianID = #clinicianid';
let request = connection.request(sql);
request.addParameter('username',db.TYPES.VarChar,params.username);
request.addParameter('clinicianid',db.TYPES.Int,uid);
yield connection.execSql();
} else {
if (!/^\S{4,}$/.test(params.passwd1)) {
response.end(JSON.stringify(
{status: false, passwd1: false,passwd2: true}
));
return;
}
let request = connection.request('SetPassword');
request.addParameter('userID',db.TYPES.Int,uid);
request.addParameter('username',db.TYPES.NVarChar,params.username);
request.addParameter('password',db.TYPES.VarChar,params.passwd1);
yield connection.callProcedure();
}
response.end(JSON.stringify({status: true}));
}).catch(err => {
logger('database',err.message);
response.end(JSON.stringify({status: false,passwd1: false,passwd2: false}));
});
Notice how I just program it like normal synchronous particularly at
yield connection.execSql and at yield connection.callProcedure
The db.exec function is a fairly typical Promise based generator
exec(generator) {
var self = this;
var it;
return new Promise((accept,reject) => {
var myConnection;
var onResult = lastPromiseResult => {
var obj = it.next(lastPromiseResult);
if (!obj.done) {
obj.value.then(onResult,reject);
} else {
if (myConnection) {
myConnection.release();
}
accept(obj.value);
}
};
self._connection().then(connection => {
myConnection = connection;
it = generator(connection); //This passes it into the generator
onResult(); //starts the generator
}).catch(error => {
reject(error);
});
});
}
Sometimes code would like to know if a particular function (or children) are running or not. For instance, node.js has domains which works for async stuff as well (not sure if this includes async functions).
Some simple code to explain what I need would by like this:
inUpdate = true;
try {
doUpdate();
} finally {
inUpdate = false;
}
This could then be used something like:
function modifyThings() {
if (inUpdate) throw new Error("Can't modify while updating");
}
With the advent of async this code breaks if the doUpdate() function is asynchronous. This was of course already true using callback-style functions.
The doUpdate function could of course be patched to maintain the variable around every await, but even if you have control over the code, this is cumbersome and error prone and this breaks when trying to track async function calls inside doUpdate.
I tried monkey-patching Promise.prototype:
const origThen = Promise.prototype.then;
Promise.prototype.then = function(resolve, reject) {
const isInUpdate = inUpdate;
origThen.call(this, function myResolve(value) {
inUpdate = isInUpdate;
try {
return resolve(value);
} finally {
inUpdate = false;
}
}, reject);
}
Unfortunately this doesn't work. I'm not sure why, but the async continuation code ends up running outside of the resolve call stack (probably using a microtask).
Note that it's not enough to simply do:
function runUpdate(doUpdate) {
inUpdate = true;
doUpdate.then(() => inUpdate = false).catch(() => inUpdate = false);
}
The reason is:
runUpdate(longAsyncFunction);
console.log(inUpdate); // incorrectly returns true
Is there any way to track something from outside an async function so it's possible to tell if the function called, or any of its descendant calls are running?
I know that it's possible to simulate async functions with generators and yield, in which case we have control over the call stack (since we can call gen.next()) but this is a kludge which the advent of async functions just got around to solving, so I'm specifically looking for a solution that works with native (not Babel-generated) async functions.
Edit: To clarify the question: Is there's a way for outside code to know if a particular invocation of an async function is running or if it is suspended, assuming that this code is the caller of the async function. Whether it's running or not would be determined by a function that ultimately is called by the async function (somewhere in the stack).
Edit: To clarify some more: The intended functionality would be the same as domains in node.js, but also for the browser. Domains already work with Promises, so async functions probably work as well (not tested).
This code allows me to do what I want to a certain extent:
function installAsyncTrack() {
/* global Promise: true */
if (Promise.isAsyncTracker) throw new Error('Only one tracker can be installed');
const RootPromise = Promise.isAsyncTracker ? Promise.rootPromise : Promise;
let active = true;
const tracker = {
track(f, o, ...args) {
const prevObj = tracker.trackObj;
tracker.trackObj = o;
try {
return f.apply(this, args);
} finally {
tracker.trackObj = prevObj;
}
},
trackObj: undefined,
uninstall() {
active = false;
if (Promise === AsyncTrackPromise.prevPromise) return;
if (Promise !== AsyncTrackPromise) return;
Promise = AsyncTrackPromise.prevPromise;
}
};
AsyncTrackPromise.prototype = Object.create(Promise);
AsyncTrackPromise.rootPromise = RootPromise;
AsyncTrackPromise.prevPromise = Promise;
Promise = AsyncTrackPromise;
AsyncTrackPromise.resolve = value => {
return new AsyncTrackPromise(resolve => resolve(value));
};
AsyncTrackPromise.reject = val => {
return new AsyncTrackPromise((resolve, reject) => reject(value));
};
AsyncTrackPromise.all = iterable => {
const promises = Array.from(iterable);
if (!promises.length) return AsyncTrackPromise.resolve();
return new AsyncTrackPromise((resolve, reject) => {
let rejected = false;
let results = new Array(promises.length);
let done = 0;
const allPromises = promises.map(promise => {
if (promise && typeof promise.then === 'function') {
return promise;
}
return new AsyncTrackPromise.resolve(promise);
});
allPromises.forEach((promise, ix) => {
promise.then(value => {
if (rejected) return;
results[ix] = value;
done++;
if (done === results.length) {
resolve(results);
}
}, reason => {
if (rejected) return;
rejected = true;
reject(reason);
});
});
});
};
AsyncTrackPromise.race = iterable => {
const promises = Array.from(iterable);
if (!promises.length) return new AsyncTrackPromise(() => {});
return new AsyncTrackPromise((resolve, reject) => {
let resolved = false;
if (promises.some(promise => {
if (!promise || typeof promise.then !== 'function') {
resolve(promise);
return true;
}
})) return;
promises.forEach((promise, ix) => {
promise.then(value => {
if (resolved) return;
resolved = true;
resolve(value);
}, reason => {
if (resolved) return;
resolved = true;
reject(reason);
});
});
});
};
function AsyncTrackPromise(handler) {
const promise = new RootPromise(handler);
promise.trackObj = tracker.trackObj;
promise.origThen = promise.then;
promise.then = thenOverride;
promise.origCatch = promise.catch;
promise.catch = catchOverride;
if (promise.finally) {
promise.origFinally = promise.finally;
promise.finally = finallyOverride;
}
return promise;
}
AsyncTrackPromise.isAsyncTracker = true;
function thenOverride(resolve, reject) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origThen.apply(this, arguments);
return this.origThen.call(
this,
myResolver(trackObj, resolve),
reject && myResolver(trackObj, reject)
);
}
function catchOverride(reject) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
return this.origCatch.call(
this,
myResolver(trackObj, reject)
);
}
function finallyOverride(callback) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
return this.origCatch.call(
this,
myResolver(trackObj, reject)
);
}
return tracker;
function myResolver(trackObj, resolve) {
return function myResolve(val) {
if (trackObj === undefined) {
return resolve(val);
}
RootPromise.resolve().then(() => {
const prevObj = tracker.trackObj;
tracker.trackObj = trackObj;
RootPromise.resolve().then(() => {
tracker.trackObj = prevObj;
});
});
const prevObj = tracker.trackObj;
tracker.trackObj = trackObj;
try {
return resolve(val);
} finally {
tracker.trackObj = prevObj;
}
};
}
}
tracker = installAsyncTrack();
function track(func, value, ...args) {
return tracker.track(func, { value }, value, ...args);
}
function show(where, which) {
console.log('At call', where, 'from', which, 'the value is: ', tracker.trackObj && tracker.trackObj.value);
}
async function test(which, sub) {
show(1, which);
await delay(Math.random() * 100);
show(2, which);
if (sub === 'resolve') {
await Promise.resolve(test('sub'));
show(3, which);
}
if (sub === 'call') {
await test(which + ' sub');
show(3, which);
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
track(test, 'test1');
track(test, 'test2');
track(test, 'test3', 'resolve');
track(test, 'test4', 'call');
It replaces the native Promise with my own. This promise stores the current context (taskObj) on the promise.
When the .then callback or its ilk are called, it does the following:
It creates a new native promise that immediately resolves. This adds a new microtask to the queue (according to spec, so should be reliable).
It calls the original resolve or reject. At least in Chrome and Firefox, this generates another microtask onto the queue that will run next part of the async function. Not sure what the spec has to say about this yet. It also restores the context around the call so that if it's not await that uses it, no microtask gets added here.
The first microtask gets executed, which is my first (native) promise being resolved. This code restores the current context (taskObj). It also creates a new resolved promise that queues another microtask
The second microtask (if any) gets executed, running the JS in the async function to until it hits the next await or returns.
The microtask queued by the first microtask gets executed, which restores the context to what it was before the Promise resolved/rejected (should always be undefined, unless set outside a tracker.track(...) call).
If the intercepted promise is not native (e.g. bluebird), it still works because it restores the state during the resolve(...) (and ilk) call.
There's one situation which I can't seem to find a solution for:
tracker.track(async () => {
console.log(tracker.taskObj); // 'test'
await (async () => {})(); //This breaks because the promise generated is native
console.log(tracker.taskObj); // undefined
}, 'test')
A workaround is to wrap the promise in Promise.resolve():
tracker.track(async () => {
console.log(tracker.taskObj); // 'test'
await Promise.resolve((async () => {})());
console.log(tracker.taskObj); // undefined
}, 'test')
Obviously, a lot of testing for all the different environments is needed and the fact that a workaround for sub-calls is needed is painful. Also, all Promises used need to either be wrapped in Promise.resolve() or use the global Promise.
[is it] possible to tell if the function called, or any of its descendant calls are running?
Yes. The answer is always no. Cause there is only one piece of code running at a time. Javascript is single threaded per definition.
Don't make it any more complicated than it needs to be. If doUpdate returns a promise (like when it is an async function), just wait for that:
inUpdate = true;
try {
await doUpdate();
//^^^^^
} finally {
inUpdate = false;
}
You can also use the finally Promise method:
var inUpdate = true;
doUpdate().finally(() => {
inUpdate = false;
});
That'll do just like like your synchronous code, having inUpdate == true while the function call or any of its descendants are running. Of course that only works if the asynchronous function doesn't settle the promise before it is finished doing its thing. And if you feel like the inUpdate flag should only be set during some specific parts of the doUpdate function, then yes the function will need to maintain the flag itself - just like it is the case with synchronous code.
The following implements a control flow wrapper co enabling asynchronous code to be delineated only by the yield keyword.
Is this basically what async/await does under the hood in ESwhatever?
co(function*() {
console.log('...');
yield one();
console.log('...');
yield two();
})
function co(gFn) {
var g = gFn();
return Promise.resolve()
.then(go);
function go() {
var result = g.next();
if(result.done) {
return;
}
if(isPromise(result.value)) {
return result.value.then(go); // Promises block until resolution.
}
return Promise.resolve(result);
}
}
function isPromise(o) {
return o instanceof Promise;
}
function one() {
return new Promise(resolve => setTimeout(() => (console.log('one'), resolve()), 1000));
}
function two() {
return new Promise(resolve => setTimeout(() => (console.log('two'), resolve()), 1000));
}
Edit:
In light of the responses I updated to take into consideration return values:
co(function*() {
console.log('...');
const result1 = yield one();
console.log('result1: ', result1);
const result2 = yield two();
console.log('result2: ', result2);
const result3 = yield[one(), two()];
console.log('result3: ', result3);
const result4 = yield{
one: one(),
two: two()
};
console.log('result4: ', result4);
})
function co(gFn) {
var g = gFn();
return Promise.resolve().then(go);
function go() {
var result = g.next(...arguments);
if (isPromise(result.value)) {
return result.value.then(go);
}
if (Array.isArray(result.value)) {
return Promise.all(result.value).then(go);
}
if (isObject(result.value)) {
var o = {};
var promises = Object.keys(result.value).map(k=>result.value[k].then(r=>o[k] = r));
return Promise.all(promises).then(()=>o).then(go);
}
return Promise.resolve(result);
}
}
function isPromise(o) {
return o instanceof Promise;
}
function isObject(val) {
return val && (Object === val.constructor);
}
function one() {
return new Promise(resolve=>setTimeout(()=>(console.log('one'),
resolve('result 1')), 1000));
}
function two() {
return new Promise(resolve=>setTimeout(()=>(console.log('two'),
resolve('result 2')), 1000));
}
Is this basically what async/await does under the hood in ESwhatever?
Not really. It's a different approach for doing sorta the same thing. What async/await turn into is more like
async function foo() {
const bar = await Bar();
bar++;
const baz = await Baz(bar);
return baz;
}
becomes
function foo() {
return Bar()
.then(bar => {
bar++;
return Baz(bar)
.then(baz => {
return baz;
});
});
}
Stage 3 Draft / January 26, 2016
Async Functions provides examples of three patterns; Promise; Generator; Async Functions; where the distinct approaches essentially produce the same result
Examples#
Take the following example, first written using Promises. This code
chains a set of animations on an element, stopping when there is an
exception in an animation, and returning the value produced by the
final succesfully executed animation.
function chainAnimationsPromise(elem, animations) {
let ret = null;
let p = currentPromise;
for(const anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
})
}
return p.catch(function(e) {
/* ignore and keep going */
}).then(function() {
return ret;
});
}
Already with promises, the code is much improved from a straight callback style, where this sort of looping and exception handling is
challenging.
Task.js and similar libraries offer a way to use generators to further
simplify the code maintaining the same meaning:
function chainAnimationsGenerator(elem, animations) {
return spawn(function*() {
let ret = null;
try {
for(const anim of animations) {
ret = yield anim(elem);
}
} catch(e) { /* ignore and keep going */ }
return ret;
});
}
This is a marked improvement. All of the promise boilerplate above and beyond the semantic content of the code is removed, and the
body of the inner function represents user intent. However, there is
an outer layer of boilerplate to wrap the code in an additional
generator function and pass it to a library to convert to a promise.
This layer needs to be repeated in every function that uses this
mechanism to produce a promise. This is so common in typical async
Javascript code, that there is value in removing the need for the
remaining boilerplate.
With async functions, all the remaining boilerplate is removed,
leaving only the semantically meaningful code in the program text:
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(const anim of animations) {
ret = await anim(elem);
}
} catch(e) { /* ignore and keep going */ }
return ret;
}