-
-
Save bmeck/5146e40d71bbd57ec9ec to your computer and use it in GitHub Desktop.
Await Example: function! is the awaitable generator syntax, you need sweet.js and some ES6 Promises/generators for this to work
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ); | |
} | |
); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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