0

I'm trying to understand a core concept of ActionHero async/await and hitting lots of walls. Essentially, in an action, why does this return immediately, rather than 500ms later?

async run (data) {
  setTimeout(() => data.response.outcome = 'success',500)
}

Clarifying edit: this question was more about async execution flow and promise fulfillment than about the literal use of setTimeout(). Its not really specific to ActionHero but that's the pattern AH uses and was my first exposure to the concepts. The answer provided clarifies that some functions have to be wrapped in a promise so they can be await-ed and that there are multiple ways to do that.

1 Answers1

0

Because you didn't wait for it to finish.

basically you need to await the setTimeout.

async run (data) {
  await setTimeout(() => data.response.outcome = 'success',500)
}

but that doesn't work because setTimeout is not a promise

You can use a simple sleep function that resolves after a time.

async function sleep (time) {
  return new Promise(resolve => setTimeout(resolve, time));
}

async function run (data) {
  await sleep(500);
  data.response.outcome = 'success';
}

Just like setTimeout, which is a callback api can be made into a promise, streams can be made into promises. Note in both the sleep and readFile examples I'm only using the async keyword to make things clear

  async readFile (file) {
    return new Promise((resolve, reject) => {
      let data = '';
      fs.createReadStream(file)
        .on('data', d => data += d.toString())
        .on('error', reject)
        .on('end', () => resolve(data));
    });
  }

For most functions you can skip the manual promisification and use util.promisify

   const { readFile } = require('fs');
   const { promisify } = require('util');

   const readFileAsync = promisify(readFile);

The key part is that the promises should resolve after the work is done, and that you should wait for it using either await or .then

So for instance to make things clearer the first example

async function run (data) {
  return sleep(500).then(() => data.response.outcome = 'success';);
}

and even

function run (data) {
  return sleep(500).then(() => data.response.outcome = 'success';);
}

are all the same at runtime

So to finish

async function transform (inputFile, targetWidth, targetHeight) {
  return new Promise((resolve, reject) => {
    let transformer = sharp()
      .resize(targetWidth, targetHeight)
      .crop(sharp.strategy.entropy)
      .on('info', { width, height } => console.log(`Image created with dimensions ${height}x${width}`)
      .on('error', reject)
      .on('end', resolve);
    inputFile.pipe(transformer);
  });
}
generalhenry
  • 17,227
  • 4
  • 48
  • 63
  • I thought all async functions return a Promise with whatever value the function returns. Is that not true, or is returning this kind of Promise different? Your answer works for my example, but in trying to apply it to a real function the request still completes immediately while my code continues to execute, so I'm obviously missing the point. – Jonathan Haglund Apr 27 '18 at 21:44
  • returning a promise and waiting for one are two different things. The key is the await, it says 'yield control of the execution until this promise resolves' so for things like getting data from a database you would 'await' until the data has resolved process it and then return the result. However if you try it with a callback api it will not work. If you give me an idea of what you want to do I'll post an example. – generalhenry Apr 27 '18 at 21:51
  • I'm doing a media resizing thing. Pull a jpg from S3, use sharp to process it, stream the results back to S3. This is my resizing code which might be the culprit `let transformer = sharp() .resize(targetWidth,targetHeight) .crop(sharp.strategy.entropy) .on('info', function(info) { console.log('Image created with dimensions ' + info.height + 'x' + info.width); }) .on('error', function(err) { console.log(err); }) let result = inputFile.pipe(transformer)` – Jonathan Haglund Apr 27 '18 at 21:58