mrks
mrks

Reputation: 5631

Create async function to call result later in express route

I have to get an Excel file from Box.com via API and convert the Excel to JSON. Later on this JSON will be rendered with Express and Handlebars.

I created the following function to fetch the Excel/JSON:

let getExternalFile = async () => {
  const stream = await client.files.getReadStream('123456789');
  const external = await fs.createWriteStream('/external.xlsx');
  stream.pipe(external);
  let finished = await external.on('finish', async () => {
    const excel = await excelToJson({
      source: fs.readFileSync(external.path),
      header: {
        rows: 1
      },
      columnToKey: {
        A: "edate",
        B: "esource"
      },
      sheets: ['Sheet1']
    });
    console.log(excel);
    return excel
  })
}

My problem now is that I do not know how to resolve the pending Promise when I do this:

let testData = getExternalFile();
console.log(testData); // Promise { <pending> }

I would download multiple files with such a function and would like it then to store in in my variable to pass it later to my express route.

app.get('/', (req, res) => {
    res.render('stories.ejs', {
        msg: "Welcome to stories.ejs", 
        odata: odata, 
        edata: edata,
        ...
    });
});

Do I have to wrap it in an anonymous async function to call it?

Upvotes: 0

Views: 264

Answers (1)

slebetman
slebetman

Reputation: 113994

async/await is not the panacea for all things asynchronous. They were designed to work only with Promises. Specifically, async/await does not work with event emitters. The correct implementation of getExternalFile should be:

let getExternalFile = async () => {
  const stream = client.files.getReadStream('123456789'); // no await here
  const external = fs.createWriteStream('/external.xlsx'); // no await here
  stream.pipe(external);

  let finished = new Promise ((resolve, reject) => {
    external.on('finish', async () => { // cannot use await here
      const excel = await excelToJson({
        source: fs.readFileSync(external.path),
        header: {
          rows: 1
        },
        columnToKey: {
          A: "edate",
          B: "esource"
        },
        sheets: ['Sheet1']
      });
      console.log(excel);

      resolve(excel);
    })
  });

  return finished; // return the promise so you can await it later
}

All non-promise functions (I will not call them non-"async" because it will confuse people with the difference between async asynchronous functions and non-async asynchronous functions because people sometimes use "async" to mean asynchronous and sometimes to mean the "async" keyword which are promise generating functions)... I digress... all non-promise functions don't work with await. This includes things like event emitters x.on('some_event' ... ).

For such cases you will need to wrap them in new Promise() to convert them to Promises.

Now that we've re-written the above function we can simply await the result:

app.get('/', async /* <--IMPORTANT */ (req, res) => {
    let testData = await getExternalFile(); // use await here!!

    // ...
});

If you have multiple awaits then the function may be slow:

app.get('/', async /* <--IMPORTANT */ (req, res) => {
    let testData = await getExternalFile();
    let testData2 = await getExternalFile2(); // SLOW!

    // ...
});

If you can get away with it you can execute the async functions in parallel:

app.get('/', async /* <--IMPORTANT */ (req, res) => {
    let testDataArray = await Promise.all([
        getExternalFile(), getExternalFile2()  // FAST!
    ]);

    // ...
});

Epologue: Errors

In real code you should catch errors on code you are awaiting to prevent your server from crashing:

app.get('/', async /* <--IMPORTANT */ (req, res) => {
    try {
        let testData = await getExternalFile(); // use await here!!

        // ...
    }
    catch (err) {

        // ...
    }
});

Alternatively you can use a middleware such as express-async-handler to catch async errors.

Upvotes: 2

Related Questions