Skip to content

Instantly share code, notes, and snippets.

@bmeck

bmeck/async.md Secret

Last active January 4, 2016 07:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bmeck/674e21f40fe6e9ce6304 to your computer and use it in GitHub Desktop.
Save bmeck/674e21f40fe6e9ce6304 to your computer and use it in GitHub Desktop.
Prelim spec for `async` and `await`

Looking for ways to make async and await less specific in ES.

Thought:

  1. async causes its operand to iterate and become a Promise

  2. let result be a new Promise

  3. let operand be the value immediately following async

  4. if operand is an instance of Iterator 1. if the IteratorComplete(operand) is not true

    1. let iterationResult be the result of IteratorNext(operand)
    2. fullfill result with the iterationResult 1. else fullfill result with undefined
  5. else fulfill result with operand

  6. return result

  7. await acts like a self referential yield that returns a Promise it will not resume until a non-await value is generated.

  8. let generator be the active Generator instance

  9. let operand be the value immediately following await

  10. let operandPromise be Promise.cast(operand)

  11. let result be undefined

  12. if generator[@AwaitingPromise] does not exist 1. let generator[@AwaitingPromise] be a new Promise 1. let result be generator[@AwaitingPromise]

  13. else 1. if generator[@AwaitingContinuation] does not exist

    1. let generator[@AwaitingContinuation] be a new unique value 1. let result be generator[@AwaitingContinuation]
  14. let finalPromise be generator[@AwaitingPromise]

  15. when operandPromise fulfills 1. setup a try guard 1. let iteration be the result generator.next with the corresponding value 1. if something is thrown

    1. delete generator[@AwaitingPromise] and generator[@AwaitingContinuation]
    2. reject finalPromise with the corresponding value 1. if iteration.done or iteration.value is not generator[@AwaitingContinuation]
    3. delete generator[@AwaitingPromise] and generator[@AwaitingContinuation]
    4. fulfill finalPromise with iteration.value
  16. when operandPromise rejects 1. call generator.throw with the corresponding value

  17. perform yield result

What brought this up:

function* doLogin(req) {
  // causes a promise (finalPromise) to be yielded
  //
  // operand is treated as a promise (innerPromise)
  // continues to execute after the innerPromise resolves
  // does not resolve finalPromise
  var session = await login(req); 
  // we are already awaiting so we continue
  //
  // operand is treated as a promise (innerPromise)
  // continues to execute after the innerPromise resolves
  // does not resolve finalPromise
  await audit(session); 
  // non-promises will just continue execution
  // since their promise resolves immediately
  // this will however split up work across ticks (useful)
  var one_two_three = await 123;
  // this is not a value we will be awaiting
  // resolve this as the finalPromise value
  return session;
}

// meh usage
//
// no await
(async doLogin(req)).then(function (session) {
  // we got a session!
}).catch(function (e) {
  // we failed to login, or we failed the audit
});

// epic usage
//
// assumes script is running in a generator that supports await
try {
  var session = await async doLogin(req);
  // we got a session!
}
catch (e) {
  // we failed to login, or we failed the audit
}
/*
The following is a close, but not perfect shim of the `await` specified in this gist
Unfortunately we cannot refer to the active generator inside of a generator function without being wrapped and having it passed in
This results in us having to call new generatorFn() rather than generatorFn() even with all the syntactic sugar
1. Sweet.js cannot provide the reference, but the use of new can change the execution context and we can just pray all users of this use `new`
2. We cannot even test if the generator is in a "suspendedStart" or the active generator if it is passed in even if the user uses "new"
*/
// START MACRO DEFINITION
macro await {
rule {
$operand:expr
} => {
yield toYield($operand, this);
}
}
// sweet.js does not let us attach things to scope containing the macro
// just use some WeakMaps for now I guess...
var asyncMapping = new WeakMap();
var tokenMapping = new WeakMap();
function toYield($operand, gen) {
var finalPromise;
var result;
if (!asyncMapping.has(gen)) {
function withCleanup(fn) {
return function () {
asyncMapping.delete(gen);
tokenMapping.delete(gen);
return fn.apply(this, arguments);
}
}
result = new Promise(function (f,r) {
asyncMapping.set(gen, {
fulfill:withCleanup(f),
reject:withCleanup(r)
});
});
}
else {
if (!tokenMapping.has(gen)) {
tokenMapping.set(gen, {});
}
result = tokenMapping.get(gen);
}
finalPromise = asyncMapping.get(gen);
var innerPromise = Promise.cast($operand);
innerPromise.then(function (value) {
var finalPromise = asyncMapping.get(gen);
try {
var result = gen.next(value);
}
catch (e) {
finalPromise.reject(e);
return;
}
if (result.done) {
finalPromise.fulfill(result.value);
}
else if (result.value !== tokenMapping.get(gen)) {
finalPromise.fulfill(result.value);
}
}).catch(function (e) {
gen.throw(e);
});
return result;
}
// END MACRO DEFINITION
function* test() {
var one = await 1;
var two = await 2;
return 3;
}
// there ideally is no need for `new` here
x=new test().next().value.then(function (v) {
console.log(v,'END');
});
// Cannot macro `async`, no sufficient Iterator shim, can't detect if something is a generator result in browsers even...
// otherwise it would be:
//
// x = (async test(req)).then(function (v) {
// console.log(v,'END');
// });
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment