JS: Asynchronous programming

Theory: Return in asynchronous code

Let's spend a bit more time on the returning of values from asynchronous functions.

In the last lesson, we learned that asynchronous functions never return the result of an asynchronous operation:

import fs from 'fs';

const noop = () => {};
const content = fs.readFile('./myfile', 'utf-8', noop);
console.log(content); // undefined

And the only way to get a result is to describe the logic in the callback. Then we should ask what happens if we make a return inside the callback. What will this lead to:

import fs from 'fs';

const content = fs.rea dFile('./myfile', 'utf-8', (_error, data) => {
  // doing something
  return data;
});
console.log(content); // undefined

As a result, nothing changes because this return isn't used by anything. It does not mean that the return instruction is useless in asynchronous code.

On the contrary, it's helpful as a way to interrupt code execution and not return the result:

import fs from 'fs';

const content = fs.readFile('./myfile', 'utf-8', (_error, data) => {
  if (data === '') {
    return;
  }
  // doing something with the data
});
console.log(content); // undefined

This pattern is called guard expression.

All the information above applies to asynchronous functions which we wrote on our own.

An asynchronous function is any function that has at least one asynchronous operation inside it. Without exception. Even if in addition to the asynchronous operation, it also performs a synchronous operation, for example, text modification. So, every asynchronous function must take a callback as its only way of ordering events and tracking completion.

Let's write an asynchronous wrapper function to read the file, which, besides the reading itself, does a little cleaning by removing leading and trailing spaces from the content.

Since our function is asynchronous, it must take a callback function as an input, and that function will be called at the end of the operation.

This function should have the generally accepted signature, with the first parameter as an error and the second as the data itself. Our asynchronous function cannot return data using return:

import fs from 'fs';

const readFileWithTrim = (filepath, cb) => {
  fs.readFile(filepath, 'utf-8', (_error, data) => {
    // Error handling hasn't been considered yet
    // Pass null as the first parameter when calling a callback function
    cb(null, data.trim());
  })
}

readFileWithTrim('./myfile', (_error, data) => console.log(data));

This process is recursive, so any function that internally works with an asynchronous function becomes asynchronous and starts taking a callback function as the input.

Why does this happen? Why can't you perform an asynchronous operation internally without returning it outside the function in any way? In this case, you can't:

  • Use the result of an asynchronous function because the data comes to the callback function in a different call stack
  • Find out whether the operation has finished, whether it was successful

We'll look at all of this in future lessons.

Recommended programs

Completed

0 / 15