Skip to content

Instantly share code, notes, and snippets.

@codeactual
Last active January 18, 2018 01:20
Show Gist options
  • Select an option

  • Save codeactual/a026f58fb0185d6532458abeba05165a to your computer and use it in GitHub Desktop.

Select an option

Save codeactual/a026f58fb0185d6532458abeba05165a to your computer and use it in GitHub Desktop.
async/await notes

async functions

general

  • await causes the function to pause until a Promise is fulfilled or rejected, and to resume execution of the async function after fulfillment. When resumed, the value of the await expression is that of the fulfilled Promise.
    • If the Promise is rejected, the await expression throws the rejected value.
    • If the value of the expression following the await operator is not a Promise, it's converted to a resolved Promise.

refs

example: how to perform some operations in parallel inside async functions, but launch it from inside a non-async function, via Promise.all

// https://stackoverflow.com/questions/42489918/async-await-inside-arraymap
const someFunction = (myArray) => {
  const promises = myArray.map(async (myValue) => {
    return {
      id: "my_id",
      myValue: await service.getByValue(myValue)
    }
  });
  return Promise.all(promises);
}

demo: how return values are promisified by the JS engine

Async functions always return a promise, whether you use await or not. That promise resolves with whatever the async function returns, or rejects with whatever the async function throws. -- https://developers.google.com/web/fundamentals/primers/async-functions

// wait ms milliseconds
function wait(ms) {
  return new Promise(r => setTimeout(r, ms));
}

async function hello() {
  await wait(500);
  return 'world';
}
…calling hello() returns a promise that fulfills with "world".

async function foo() {
  await wait(500);
  throw Error('bar');
}
…calling foo() returns a promise that rejects with Error('bar').

promises

general

  • Promise.resolve / Promise.reject: for use cases that need an already resolved/rejected promise, examples:
    • make func1 and func2 happen sequentially: [func1, func2].reduce((p, f) => p.then(f), Promise.resolve()); by reduce-ing them to the equivalent Promise.resolve().then(func1).then(func2);
      • could also be done via a reusable composeAsync function:
        let applyAsync = (acc,val) => acc.then(val);
        let composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
        
      • ... but the clearest way is to use an async/await equivalent loop:
        for (let f of [func1, func2]) {
          await f();
        }
        
  • doSomething().then(successCallback, failureCallback)
  • guarantees
    • callbacks never called before completion of current event loop run
    • callbacks added via then() will still be called, even if they're added after the promise's work (and success/failure "response" to other callbacks added pre-completion) has happened
    • multiple callbacks can be added by then() chaining
      • executed in insertion order

refs

then(successCallback, failureCallback) / catch(failureCallback)

  • the return value each then invocation is nuanced and highly conditional, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
    • for example, if the success/reject handler returns a non-error value, then returns a promise that gets resolved with that value
    • for example, if the success/reject handler returns a resolved promise, then returns a promise that gets resolved with the value of the promise returned by that handler
    • etc.
  • functions passed to then will never be called synchronously, the current event loop queue will always be drained first, e.g.:
    Promise.resolve().then(() => console.log(2));
    console.log(1); // 1, 2
    
  • only way to communicate an error (to affect the chain) is via throw (i.e. can't just return an Error)
  • catch(failureCallback) is actually just short for then(null, failureCallback), examples of how that's true in practice
    • then can be added in a chain after a catch
    • if an Error is thrown from inside catch, the chain continues as if it was thrown from a then in the same position: an immediately following then will get skipped and a subsequent catch can catch that Error from the earlier catch

example: it's possible to add then() after a catch() in order to have something happen later, regardless of errors

new Promise((resolve, reject) => {
    console.log('Initial');

    resolve();
})
.then(() => {
    throw new Error('Something failed');

    console.log('Do this');
})
.catch(() => {
    console.log('Do that');
})
.then(() => {
    console.log('Do this whatever happened before');
});

// Outputs
Initial
Do that
Do this whatever happened before

example: promise chain vs similar flow via async library

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

async.series([
  function(callback) {
    // do some stuff ...
    callback(null, 'one');
  },
  function(callback) {
    // do some more stuff ...
    callback(null, 'two');
  }
],
// optional callback
function(err, results) {
  // results is now equal to ['one', 'two']
});

compressed chain example using arrow functions

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

example: callback hell vs promises vs async/await

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

doSomething()
.then(result => doSomethingElse(value))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);

async function foo() {
  try {
    let result = await doSomething();
    let newResult = await doSomethingElse(result);
    let finalResult = await doThirdThing(newResult);
    console.log(`Got the final result: ${finalResult}`);
  } catch(error) {
    failureCallback(error);
  }
}

promisify / promisification

via node's util.promisify

  • requires the input function to either:
    • have a (err, value) signature
    • OR have a util.promisify.custom property that is a function with a (value) signature and returns a Promise, which covers the case where the target function's signature doesn't align with the required (err, value) format of util.promisify inputs that are functions

example: util.promisify.custom

const util = require('util');

function doSomething(foo, callback) {
  console.log('doSomething foo:', foo);
  callback()
}

doSomething[util.promisify.custom] = function(foo) {
  console.log('doSomething[util.promisify.custom] foo:', foo)

  return new Promise(function(resolve, reject) {
    doSomething(foo, resolve, reject);
  });
};

const promisified = util.promisify(doSomething);

async function callit() {
  await promisified('abc')
  console.log('after await');
}

(async () => {
  await callit()
  console.log('after callit');
})();

refs

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