Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Express.js Async/Await

Async/Await is a modern approach to handle asynchronous operations in JavaScript, providing a cleaner and more readable syntax compared to traditional callbacks and promises. This guide covers key concepts, examples, and best practices for using async/await in Express.js applications.

Key Concepts of Async/Await

  • Async Function: A function declared with the async keyword that returns a promise.
  • Await: A keyword used inside an async function to pause execution and wait for a promise to resolve or reject.
  • Error Handling: Use try/catch blocks to handle errors in async functions.

Using Async/Await

Convert asynchronous operations to use async/await for better readability and maintainability:

Example: Basic Async/Await

// async-await.js
const express = require('express');
const fs = require('fs').promises;
const app = express();
const port = 3000;

app.get('/read-file', async (req, res) => {
    try {
        const data = await fs.readFile('example.txt', 'utf8');
        res.send(data);
    } catch (err) {
        res.status(500).send('Error reading file');
    }
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Chaining Async/Await

Chain multiple asynchronous operations using async/await:

Example: Chaining Async/Await

// chaining-async-await.js
const express = require('express');
const fs = require('fs').promises;
const app = express();
const port = 3000;

app.get('/read-files', async (req, res) => {
    try {
        const data1 = await fs.readFile('file1.txt', 'utf8');
        const data2 = await fs.readFile('file2.txt', 'utf8');
        const data3 = await fs.readFile('file3.txt', 'utf8');
        res.send(data1 + data2 + data3);
    } catch (err) {
        res.status(500).send('Error reading files');
    }
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Using Promise.all with Async/Await

Use Promise.all with async/await to run multiple promises concurrently:

Example: Promise.all with Async/Await

// promise-all-async-await.js
const express = require('express');
const fs = require('fs').promises;
const app = express();
const port = 3000;

app.get('/read-files', async (req, res) => {
    try {
        const files = await Promise.all([
            fs.readFile('file1.txt', 'utf8'),
            fs.readFile('file2.txt', 'utf8'),
            fs.readFile('file3.txt', 'utf8')
        ]);
        res.send(files.join('\n'));
    } catch (err) {
        res.status(500).send('Error reading files');
    }
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Error Handling with Async/Await

Handle errors in async functions using try/catch blocks:

Example: Error Handling with Async/Await

// async-await-error.js
const express = require('express');
const fs = require('fs').promises;
const app = express();
const port = 3000;

app.get('/read-file', async (req, res) => {
    try {
        const data = await fs.readFile('example.txt', 'utf8');
        res.send(data);
    } catch (err) {
        res.status(500).send('Error reading file');
    }
});

// Centralized error handling middleware
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something went wrong!');
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Best Practices for Using Async/Await

  • Use Async/Await: Prefer async/await over callbacks and promises for cleaner and more readable code.
  • Handle Errors: Always use try/catch blocks to handle errors in async functions.
  • Use Promise.all: Use Promise.all with async/await to run multiple promises concurrently and wait for all of them to complete.
  • Avoid Blocking Operations: Avoid using blocking operations in your async functions to ensure your application remains responsive.
  • Chain Async Operations: Chain async operations using async/await to handle sequences of asynchronous tasks effectively.

Testing Async/Await

Test your async/await code using frameworks like Mocha, Chai, and Supertest:

Example: Testing Async/Await

// Install Mocha, Chai, and Supertest
// npm install --save-dev mocha chai supertest

// test/async-await.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const express = require('express');
const fs = require('fs').promises;

const app = express();

app.get('/read-file', async (req, res) => {
    try {
        const data = await fs.readFile('example.txt', 'utf8');
        res.send(data);
    } catch (err) {
        res.status(500).send('Error reading file');
    }
});

describe('GET /read-file', () => {
    it('should read the file content', (done) => {
        request(app)
            .get('/read-file')
            .expect(200)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.text).to.be.a('string');
                done();
            });
    });

    it('should return 500 if there is an error reading the file', (done) => {
        // Temporarily rename the file to simulate an error
        fs.rename('example.txt', 'example_tmp.txt')
            .then(() => {
                request(app)
                    .get('/read-file')
                    .expect(500)
                    .end((err, res) => {
                        if (err) return done(err);
                        expect(res.text).to.equal('Error reading file');
                        // Restore the file after the test
                        fs.rename('example_tmp.txt', 'example.txt').then(() => done());
                    });
            });
    });
});

// Define test script in package.json
// "scripts": {
//   "test": "mocha"
// }

// Run tests with NPM
// npm run test

Key Points

  • Async Function: A function declared with the async keyword that returns a promise.
  • Await: A keyword used inside an async function to pause execution and wait for a promise to resolve or reject.
  • Error Handling: Use try/catch blocks to handle errors in async functions.
  • Follow best practices for using async/await, such as preferring async/await over callbacks and promises, handling errors, using Promise.all, avoiding blocking operations, and chaining async operations.

Conclusion

Async/Await is a modern approach to handle asynchronous operations in JavaScript, providing a cleaner and more readable syntax compared to traditional callbacks and promises. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively manage asynchronous operations using async/await in your Express.js applications. Happy coding!