Skip to content

Instantly share code, notes, and snippets.

@bmeck
Last active January 4, 2016 07:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bmeck/72a0f4f448f20cf00f8c to your computer and use it in GitHub Desktop.
Save bmeck/72a0f4f448f20cf00f8c to your computer and use it in GitHub Desktop.

Assumptions:

  1. async causes it's operand to become a Promise, it will [[call]] the operand if it is define, and start a generator if that is the result.
  2. await acts like a yield that performs Promise.cast(operand)
  • await will resume (.next the generator) when the promise resolves
  • this would allow an async function* to queue async tasks while keeping a linear codebase

What brought this up:

async function* doLogin(req) {
  try {
    var session = await login(req); // wait until the promise resolves
    gotoProfile(session);
  }
  catch (e) {
    printError(e);
    gotoLogin();
  }
  // implicit return undefined
}
// usage
doLogin(req).then(...);

You can think of it in Promises and Generators as similar to:

function doLogin(req) {
  return new Promise(function (f, r) {
    var gen = doLogin_(req);
    gen.next();
    
    function doLogin_*(req) {
      try {
        login(req).then(function (session) {
          gen.next(session);
        })
        .catch(function (e) {gen.throw(e);});
        try {
          var session = yield; // waiting on our promise to finish...
          gotoProfile(session);
        }
        catch (e) {
          printError(e);
          gotoLogin();
        }
        f();
      }
      catch (e) {r(e);}
    }
  });
}
doLogin(req).then(end).catch(die); // handle this as a promise

Allan's simplification by use of new, keeps gen in doLogin_'s scope

function doLogin(req) {
  return new Promise(function (f, r) {
    new doLogin_(req).next();
    
    function doLogin_*(req) {
      var gen = this;
      try {
        login(req).then(function (session) {
          gen.next(session);
        })
        .catch(function (e) {gen.throw(e);});
        try {
          var session = yield; // waiting on our promise to finish...
          gotoProfile(session);
        }
        catch (e) {
          printError(e);
          gotoLogin();
        }
        f();
      }
      catch (e) {r(e);}
    }
  });
}
doLogin(req).then(end).catch(die); // handle this as a promise

Brendan's suggestion to use a library (task.js) - more as a representation of what can be done for simplicity, the library code won't match nicely with source maps / forces having more in the namespace / loading the lib.

spawn(function*()  {
  try  {
    var  session  =  yieldlogin(req);  // wait until the promise resolves
    gotoProfile(session);
  }
  catch  (e)  {
    printError(e);
    gotoLogin();
  }
  // implicit return undefined
});
@spion
Copy link

spion commented Jan 23, 2014

Bluebird's Promise.coroutine

var doLogin = Promise.coroutine(function* () {
  // generators do not have references to themselves...
  try {
    var session = yield login(req);
    gotoProfile(session);
  } 
  catch (e) {
    printError(e);
    gotoLogin();
  }  
});

Copy link

ghost commented Jan 23, 2014

It's probably better to separate out the machinery for executing an async function rather than transpiling it out like that.

An async function:

function! doLogin(req) {
  try {
    var session = await login(req);
    gotoProfile(session);
  } catch (e) {
    printError(e);
    gotoLogin();
  }
}

With a simple transformation:

function doLogin(req) {
  return run(_doLogin(req));
}

function* _doLogin(req) {
  try {
    var session = yield login(req);
    gotoProfile(session);
  } catch (e) {
    printError(e);
    gotoLogin();
  }
}

Using this as the plumbing:

function run(gen) {
  function _next(v) { return handle(gen.next(v)); }
  function _throw(e) { return handle(gen.throw(e)); }

  function handle(result) {
    var value = result.value;
    if (!result.done && value && typeof value.then === "function") {
      return value.then(_next, _throw);
    }
    return value;
  }

  return new Promise(function(resolve, reject) {
    Promise.cast(_next()).then(resolve, reject);
  });
}

I made a sweet.js macro that does the transformation: https://gist.github.com/Benvie/8327917

@bmeck
Copy link
Author

bmeck commented Jan 24, 2014

My inclination is to create self contained transpilers, having to introduce an external function call/library makes me wary. If this were intended as a library I would feel ok, but as a syntax/language extension it would be odd to introduce a function like that without some lifting to ensure no name clashes / source maps are valuable still.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment