Skip to content

Instantly share code, notes, and snippets.

@bmeck
Last active Jan 4, 2016
Embed
What would you like to do?
Await Example: function! is the awaitable generator syntax, you need sweet.js and some ES6 Promises/generators for this to work
macro await {
case { $macro $operand:expr } => {
throw new Error('await must occur within an async function');
}
}
macro function! {
case { $macro $id ($args ...) { $body ...} } => {
var body = #{ $body ... };
function expand(orig) {
var body = orig.concat();
// traverse body to do replacements of await/yield tokens
for (var i = 0; i < body.length; i++) {
var token = body[i].token;
if (token.type === parser.Token.Identifier || token.type === parser.Token.Keyword) {
// skip any nested block scope
if (token.value === 'function') {
var expr=getExpr(body.slice(i));
i--;
i+=expr.result.length;
}
// replace yield and await
else if (token.value === 'await' || token.value === 'yield') {
// make sure they have an expression following them
var expr = getExpr(body.slice(i+1));
if (expr.success) {
var yieldWord = makeKeyword('yield', #{ $id });
var arr = makeDelim('[]', [makeValue(token.value, #{ $id }), makePunc(',', #{ $id })].concat(expr.result), #{ $id });
body.splice(i, 1 + expr.result.length, yieldWord, arr);
}
}
}
// if we encounter lexical scope, expand it
else if (token.inner) {
token.inner = expand(token.inner);
}
}
return body;
}
letstx $nbody ... = expand(body);
return #{
function $id () {
return function proxy(innerFn, innterFnThis, innerFnArguments) {
function* scheduleGenerator() {
// Setup to match prototype / construction
proxy.prototype = $id.prototype;
var innerGenerator = innerFn.apply(innterFnThis, innerFnArguments);
// paranoia
innerGenerator.constructor = this;
// simple linked list of queued promises
var head = null;
var tail = null;
// calls the inner generator .next and figures out next course of action
function nextForValue(v) {
iterate(function () {
return innerGenerator.next(v);
});
}
// calls the inner generator .throw and figures out next course of action
function throwForValue(e) {
iterate(function () {
return innerGenerator.throw(e);
});
}
// calls the inner generator somehow and figures out next course of action
function iterate(how) {
var innerResult;
try {
// invoking the inner generator
innerResult = how();
}
catch (e) {
// generator threw
head.reject(e);
return;
}
if (!innerResult.done) {
// generator used await
if (innerResult.value[0] === 'await') {
Promise.cast(innerResult.value[1]).then(nextForValue).catch(throwForValue);
}
// generator used yield
else {
head.fulfill(innerResult.value[1]);
}
}
// generator used return
else {
head.fulfill(innerResult.value);
}
}
// method for promise to dequeue on resolution, only used by head
function dequeue() {
// if we are the last promise we should cleanup
if (head === tail) {
head = tail = null;
}
else {
head = head.next;
head.start();
}
}
// queues a promise and value for execution and resolution
function enqueue(v) {
var helper;
// promise we will return to person calling our outer generator (async function)
var outerPromise = new Promise(function (f,r) {
helper = {
start: function start() {
nextForValue(v);
},
fulfill: function (resultValue) {
f(resultValue);
dequeue();
},
reject: function (e) {
r(e);
dequeue();
}
};
// if we are not running already we should be
if (!head) {
head = helper;
tail = helper;
head.start(v);
}
// we are running already, put this on the tail
else {
tail.next = helper;
tail = helper;
}
});
return outerPromise;
}
// running
var passedValue = void 0;
while (true) {
passedValue = yield enqueue(passedValue);
}
}
return scheduleGenerator();
// modified original function
}(function* ($args ...) {
$nbody ...
}, this, arguments);
}
}
}
}
// some testcases
// out of asyncFn this is a syntax error
// await 'test await syntax error outside async function'
function! asyncFn() {
function* nestingTest() {
// nested inside of asyncFn this is a syntax error;
// await 'test await syntax error nesting'
yield "should not be transpiled in nested functions";
}
console.log(arguments, 'PASSED IN AS ASYNC FUNCTION ARGUMENTS');
console.log(await 'first await operand', 'PASSED IN AS FIRST AWAIT VALUE');
console.log(await 'second await operand', 'PASSED IN AS SECOND AWAIT VALUE');
console.log(yield 'first yield operand', 'PASSED IN AS FIRST YIELD VALUE');
console.log(yield 'second yield operand', 'PASSED IN AS SECOND YIELD VALUE');
console.log(yield new Promise(function (f, r) {
setTimeout(function () {
f('promises can be yielded without awaiting, state is matched by outer promise');
}, 2000);
}), 'PASSED IN AS THIRD YIELD VALUE');
return 'return value';
}
var iter = asyncFn('first async function argument', 'second async function argument');
var promises = [iter.next(),iter.next('second next operand'),iter.next('third next operand'),iter.next('fourth next operand'),iter.next('fifth next operand')];
promises.forEach(function (result, i) {
var promise = result.value;
var onFulfill = console.log.bind(console, 'promise', i, 'fulfilled as');
var onReject = console.error.bind(console, 'promise', i, 'rejected as');
promise.then(onFulfill, onReject);
});
function game ( ) {
return function proxy ( innerFn , innterFnThis , innerFnArguments ) {
function * scheduleGenerator ( ) {
scheduleGenerator . prototype = genFn . prototype;
proxy . prototype = game . prototype;
var innerGenerator = innerFn . apply ( innterFnThis , innerFnArguments );
innerGenerator . constructor = this;
var head = null;
var tail = null;
function nextForValue ( v ) {
iterate ( function ( ) {
return innerGenerator . next ( v );
}
);
}
function throwForValue ( e ) {
iterate ( function ( ) {
return innerGenerator . throw ( e );
}
);
}
function iterate ( how ) {
var innerResult;
try {
innerResult = how ( );
}
catch ( e ) {
head . reject ( e );
return;
}
if ( ! innerResult . done ) {
if ( innerResult . value [ 0 ] === "await" ) {
Promise . cast ( innerResult . value [ 1 ] ) . then ( nextForValue ) . catch ( throwForValue );
}
else {
head . fulfill ( innerResult . value [ 1 ] );
}
}
else {
head . fulfill ( innerResult . value );
}
}
function dequeue ( ) {
if ( head === tail ) {
head = tail = null;
}
else {
head = head . next;
head . start ( );
}
}
function enqueue ( v ) {
var helper;
var outerPromise = new Promise ( function ( f , r ) {
helper = {
start : function start ( ) {
nextForValue ( v );
}
, fulfill : function ( resultValue ) {
f ( resultValue );
dequeue ( );
}
, reject : function ( e ) {
r ( e );
dequeue ( );
}
}
;
if ( ! head ) {
head = helper;
tail = helper;
head . start ( v );
}
else {
tail . next = helper;
tail = helper;
}
}
);
return outerPromise;
}
var passedValue = void 0;
while ( true ) {
passedValue = yield enqueue ( passedValue );
}
}
return scheduleGenerator ( );
}
( function * ( credentials ) {
var session = yield [ "await" , getSession ( credentials ) ];
var assets = loadLocalAssets ( session );
var connection = establishGameServerConnection ( session );
yield [ "await" , Promise . all ( [ assets , connection ] ) ];
while ( true ) {
var serverSentCommand = connection . getCommand ( );
var clientSentCommand = getClientCommand ( );
var ourCommand = yield [ "await" , Promise . race ( [ serverSentCommand , clientSentCommand ] ) ];
if ( serverSentcommand . type == "quit" ) {
return serverSentcommand . exitCode;
}
}
}
, this , arguments );
}
var ourGame = game ( [ "user" , "pass" ] ) . next ( );
ourGame . then ( function ( exitCode ) {
process . exit ( exitCode );
}
) . catch ( function ( e ) {
console . error ( e );
process . exit ( 1 );
}
);
function! game(credentials) {
// check our login
var session = await getSession(credentials);
// load local storage as a promise
var assets = loadLocalAssets(session);
// get connection to host as a promise
var connection = establishGameServerConnection(session);
// wait for both assets and connection
await Promise.all([
assets,
connection
]);
while (true) {
// server sends all the commands to client
// this is a promise
var serverSentCommand = connection.getCommand();
// object that client can use to send commands
// these are whitelisted by the connection
// - quit, logout, etc.
var clientSentCommand = getClientCommand();
// wait until the server or client gives us a command
var ourCommand = await Promise.race([
serverSentCommand,
clientSentCommand
]);
if (serverSentcommand.type == 'quit') {
return serverSentcommand.exitCode;
}
}
}
var ourGame = game(['user', 'pass']).next();
ourGame.then(function (exitCode) {
process.exit(exitCode);
}).catch(function (e) {
console.error(e);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment