Skip to content

03 Async

Jagdeep Singh edited this page May 15, 2019 · 2 revisions

File System

All methods have asynchronous and synchronous forms available.

Synchronous forms throw exceptions immediately. Can use try/catch to handle exceptions or allow them to bubble up.

Asynchronous form always takes a completion callback as its last argument. The arguments passed to the completion callback depend on the method, but the first argument is always reserved for an exception. If the operation was completed successfully, then the first argument will be null or undefined.

No guaranteed ordering, prone to errors. To create ordering, chain the callbacks.

For example, if you want to execute fs.stat before fs.rename.

fs.rename('/tmp/hello', '/tmp/world', (err) => {
  if (err) throw err;
  fs.stat('/tmp/world', (err, stats) => {
    if (err) throw err;
    console.log(`stats: ${JSON.stringify(stats)}`);
  });
});

In busy processes, the programmer is strongly encouraged to use the asynchronous versions. The synchronous versions will block the entire process until they complete -- halting all connections.

The relative path to a filename can be used. However, that this path will be relative to process.cwd().

Buffer API

fs functions support passing and receiving paths as both strings and Buffers. Buffers is intended to make it possible to work with filesystems that allow for non-UTF-8 filenames. The string API converts to and from UTF-8 automatically.

On certain file systems (such as NTFS and HFS+), filenames are always encoded as UTF-8. Passing non-UTF-8 encoded Buffers to fs functions will not work as expected on these file systems.


"Error-First" Callback

Callbacks enable a balanced, non-blocking flow of asynchronous control across modules and applications. But at scale, you need a common, reliable protocol. The "error-first" callback ("errorback", "errback", or "node-style callback") has become the standard for Node.js callbacks.

Why Standardize?

Node.js uses callbacks today using Continuation-Passing Style (CPS. In CPS, a "continuation function" (callback) is passed as an argument and is called once the rest of the code has been run. Allows different functions to asynchronously hand control back and forth across an application.

Node.js relies on asynchronous code for speed, a dependable callback pattern is crucial.

Defining An Error-First Callback

Rules:

  1. The first argument of the callback is reserved for an error object.
    • If an error occurred, it will be returned by the first err argument.
  2. The second argument of the callback is reserved for any successful response data.
    • If no error occurred, err will be set to null and any successful data will be returned in the second argument.

If something goes wrong, the first argument err will be populated with an error object containing information about the problem. How you handle this error is up to you. You can throw if you want your entire application to shutdown or you can propagate that error out to the next callback.

Err-Ception: Propagating Your Errors

When a function passes its errors to a callback it no longer has to make assumptions on how that error should be handled.

When you're consistent with this pattern, errors can be propagated up as many times as you'd like. Each callback can choose to ignore, handle, or propagate the error based on the information and context that exist at that level.

Slow Your Roll, Control Your Flow

With a solid callback protocol in hand, callbacks can be called in parallel, in a queue, in serial, or any other combination you can imagine.


Mocking is a technique to isolate test subjects by replacing dependencies with objects that you can control and inspect. A dependency can be anything your subject depends on, but it is typically a module that the subject imports.

For JavaScript, there are great mocking libraries available like testdouble and sinon, and Jest provides mocking out of the box.

When talking about mocking in Jest, we're typically talking about replacing dependencies with the Mock Function.

The Mock Function

The goal for mocking to replace something we don't control with something we do, it's important that what we replace it with has all the features we need.

The Mock Function provides features to:

  • Capture calls
  • Set return values
  • Change the implementation

The simplest way to create a Mock Function instance is with jest.fn().

Dependency Injection

One of the common ways to use the Mock Function is by passing it directly as an argument to the function you are testing. Allows you to run your test subject, then assert how the mock was called and with what arguments:

const doAdd = (a, b, callback) => {
	callback(a + b);
};
test("calls callback with arguments added", () => {
	const mockCallback = jest.fn();
	doAdd(1, 2, mockCallback);
	expect(mockCallback).toHaveBeenCalledWith(3);
});

This strategy is solid, but it requires that your code supports dependency injection. If this is not the case, so we will need tools to mock existing modules and functions instead.

Mocking Modules and Functions

There are three main types of module and function mocking in Jest:

  • jest.fn: Mock a function
  • jest.mock: Mock a module
  • jest.spyOn: Spy or mock a function

Each of these, in some way, create the Mock Function.

Clone this wiki locally