Узнайте об асинхронной природе JavaScript, который может выполнять несколько задач одновременно.

Прежде чем мы начнем изучать, что такое асинхронное программирование JavaScript, давайте сначала рассмотрим синхронное программирование JavaScript.

1. Синхронный JavaScript

JavaScript по своей природе является synchronous programming language и однопоточным. Это означает, что код не может работать параллельно и не может выполнять несколько задач одновременно. Компилятор JavaScript читает инструкции построчно и реагирует на каждое событие по порядку.

Рассмотрим этот пример:

const sayHello = (name) => {
    return `Hello, ${name}!`;
};

let name = 'Stoman';
const greet = sayHello(name);
console.log(greet); // Hello, Stoman!

Пример выше будет выполняться в следующем порядке:

  1. Мы объявляем стрелочную функцию sayHello(), которая принимает параметр имени
  2. Мы определяем переменную name, которая хранит имя, в нашем случае Stoman
  3. Мы сохраняем возвращаемое значение функции sayHello() для приветствия, которая принимает параметр имени
  4. Записываем значение приветствия в консоль

Мы ясно видим, что здесь происходит, все выполняется по порядку, и компилятор читает наш код построчно. Это может быть нормально для такой простой программы, как эта, но представьте, что вы запускаете задачу, выполнение которой займет много времени, и в то же время вам нужно увидеть результат некоторых других событий, пока вы ждете. для завершения текущей задачи. Вы не хотите ждать, пока одна вещь будет закончена первой, чтобы вы могли увидеть или сделать что-то еще.

Или, скажем, вы используете Twitter, чтобы написать что-то, определенно, вы не хотите заканчивать твит, чтобы получить новое уведомление о подписке или новое сообщение от вашего друга, вы хотите иметь возможность получать новые уведомления, новые твиты, новые сообщения и многое другое, пока вы пишете твит.

Именно поэтому Asynchronous Programming является необходимостью.

2. Асинхронный JavaScript

Асинхронное программирование — это метод программирования, который позволяет вашему коду одновременно реагировать на несколько задач. Или, другими словами, несколько задач могут выполняться параллельно.

Существуют различные способы написания асинхронных программ на JavaScript, такие как ключевые слова Async/Await, обещания и функции обратного вызова. Начнем с Callback functions.

2.1. Функции обратного вызова

Функции обратного вызова JavaScript раньше были основным способом написания асинхронного кода. Функция обратного вызова — это просто функция, которая может быть передана в другую функцию в качестве аргумента, а затем будет выполнена, как только другая функция завершит свою работу.

Посмотрите на этот пример:

const sayHello = (name, callback) => {
    console.log(`Hello, my name is ${name}.`);
    callback();
};

const introduceYourself = () => {
    console.log("I'm a software developer and design enthusiast.");
};

sayHello('Stoman', introduceYourself);
// Hello, my name is Stoman.
// I'm a software developer and design enthusiast.

Или этот пример:

const sayHello = () => {
    console.log('Hello, my name is Stoman');
};

const favouriteColor = (color) => {
    console.log(`My favuorite color is ${color}`);
};

setTimeout(sayHello, 2000);
favouriteColor('Black');
// My favuorite color is Black
// Hello, my name is Stoman

Немного расширенный пример из документации Nodejs:

const final = (someInput, callback) => {
    callback(`${someInput} and terminated by executing callback `);
};

const middleware = (someInput, callback) => {
    return final(`${someInput} touched by middleware `, callback);
};

const initiate = () => {
    const someInput = 'hello this is a function ';
    middleware(someInput, function (result) {
        console.log(result);
        // requires callback to `return` result
    });
};

initiate();
// hello this is a function touched by middleware and terminated by executing callback

Функции обратного вызова JavaScript прекрасно подходят для кода меньшего размера, но представьте, что мы хотим, чтобы вложенные асинхронные функции выполнялись одна за другой, например так:

const sendTweet = (someParams) => {
    newMessage((someParams) => {
        newNotification((someParams) => {
            getTweetAnalytics((someParams) => {
                // Maybe many more nested functions here
            });
        });
    });
};

Имея вложенные функции обратного вызова, которые должны выполнять разные задачи одну за другой, они становятся все более и более сложными для управления и чтения по мере увеличения размера и сложности вашей программы. Чтобы сделать его более читабельным и простым для понимания, мы можем использовать Promises вместо функций обратного вызова.

2.2. Обещания

Обещания были представлены в языке JavaScript в ES6 (2015) со многими другими замечательными функциями. Обещание в JavaScript — это просто объект JavaScript, который вернет значение в будущем.

Вот как выглядит обещание в JavaScript:

getTweets(someParams)
    .then( // Successful respone )
    .catch( // Rejected error );

Обещание либо возвращает ответ, либо ошибку отклонения, которая может быть выполнена в блоках .then и .catch соответственно.

const saySomething = new Promise((resolve, reject) => {
    setTimeout(() => resolve('I love Programming!'), 3000);
});

saySomething
    // Executes if there is a successful response
    .then((value) => {
        console.log(value);
    })
    // Executes if there is an error
    .catch((error) => {
        console.log(error);
    });

console.log('Log this statement firs!');
// Log this statement firs!
// I love Programming!

.then() метод принимает два аргумента; первый аргумент — это функция обратного вызова для успешного ответа, а второй аргумент — функция обратного вызова для отклоненной ошибки. Каждый .then() возвращает новое обещание, которое можно дополнительно использовать для цепочки.

Посмотрите на этот пример:

const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('foo');
    }, 300);
});

myPromise
    .then(handleFulfilledA, handleRejectedA)
    .then(handleFulfilledB, handleRejectedB)
    .then(handleFulfilledC, handleRejectedC);

Посмотрите на этот пример, где мы извлекаем данные из конечной точки API:

const getData = fetch('https://jsonplaceholder.typicode.com/posts/1');

getData
    .then((response) => {
        // Return the successful data
        return response.json();
    })
    .then((data) => {
        // If successful, log the data
        console.log(data);
    })
    .catch((error) => {
        // If error, log the error
        console.log(`Error: ${error.message}`);
    }).finally(
        const sayHello = () => {
            console.log('Logging from the finally statement.');
        }
    );

getData.then((data) => console.log('Log something else ...'));
/**
 * Output:
 * Log something else ...
 * (4) {userId: 1, id: 1, title: "sunt aut ...}
 */

Для точно такого же примера выше мы также можем использовать некоторые библиотеки, чтобы упростить получение данных, например Axios:

npm install axios --save

const axios = require('axios');

axios.get('https://jsonplaceholder.typicode.com/posts/1');

Существует также необязательный метод finally(), который может быть выполнен, когда обещание выполнено или отклонено.

const getData = fetch('https://jsonplaceholder.typicode.com/posts/1');

getData
    .then((response) => {
        // Return the successful data
        return response.json();
    })
    .then((data) => {
        // If successful, log the data
        console.log(data);
    })
    .catch((error) => {
        // If error, log the error
        console.log(`Error: ${error.message}`);
    })
    .finally(function sayHello() {
        console.log('Logging from the finally statement.');
    });

getData.then((data) => console.log('Log something else ...'));
/**
 * Output:
 * Log something else ...
 * (4) {userId: 1, id: 1, title: "sunt aut ...}
 * Logging from the finally statement.
 */

Промисы — отличный способ работы с асинхронным кодом в JavaScript, но если вы предпочитаете более чистый и читабельный способ работы с асинхронным кодом в JavaScript, вам подойдут ключевые слова Async/Await. Ключевые слова Async/Await в настоящее время широко используются во многих проектах, а также являются предпочтительным выбором для многих программистов.

2.3. Ключевое слово Async/Await

Async/Await был введен в язык JavaScript в ES2017. Ключевые слова async/await в JavaScript — это более удобный способ использования промисов, или, как говорят, это в основном синтаксический сахар для промисов. Асинхронная функция в JavaScript может быть определена с помощью ключевого слова async, аналогичным образом ключевое слово await может использоваться, чтобы указать JavaScript, что нужно вернуть результат промиса, а не возвращать сам промис.

В JavaScript использование async/await выглядит так:

// 1. With normal functions
async function getTweets(someParams) {
    await doSomethingAsynchronously;
    // Or you can store the result to another variable
    const result = await doSomething;
}

// 2. With arrow functions
const getTweets = async () => {
    await doSomethingAsynchronously;
    // Or you can store the result to another variable
    const result = await doSomething;
};

Ключевое слово await в асинхронной функции блокирует выполнение программы внутри этой функции до тех пор, пока не будет получен ответ или отклонена ошибка.

Пример:

console.log(1);
console.log(2);

const compareStrings = () => {
    const promise = new Promise((resolve, reject) => {
        const x = 'Programming';
        const y = 'Programming';
        x === y
            ? resolve('Strings are the same')
            : reject('Strings are not the same');
    });

    return promise;
};

const finalResult = async () => {
    try {
        let message = await compareStrings();
        console.log(message);
    } catch (error) {
        console.log('Error: ' + error);
    }
};

finalResult();

console.log(3);
console.log(4);

/**
 * Output:
 * 1
 * 2
 * 3
 * 4
 * Strings are the same
 */

Как видите, мы не останавливаем выполнение программы. Мы регистрируем все операторы, а ключевое слово await останавливает выполнение программы до тех пор, пока не будет ответа или ошибки.

Обработка ошибок в async/await:

const getData = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Successful response');
    }, 4000);
});

const finalResult = async () => {
    try {
        // Await the response
        let result = await getData;
        console.log(result);
    } catch (error) {
        console.log(error);
    }
};

finalResult();

console.log('Something else here!');
// Something else here!
// Successful response

Блок try-catch — отличный способ легко обработать любую ошибку в программе. Блок try получает все, что необходимо выполнить для получения успешного ответа, а блок catch обрабатывает любую ошибку, которая может возникнуть в программе.

Заключение

JavaScript — это однопоточный язык синхронного программирования, но с помощью обещаний и ключевых слов async/await JavaScript можно превратить в язык асинхронного программирования.

В этой статье мы узнали, что такое Aynchronous JavaScript, что такое функции Callack и обещания для улучшения асинхронного программирования, а затем поработали с Async/Await, который является более практичным и простым способом использования обещаний. Я надеюсь, что эта статья поможет вам понять асинхронный JavaScript, чтобы вы могли применять его в своих проектах JavaScript/Node.js.

Существует гораздо больше асинхронного программирования в JavaScript, о чем я здесь не упомянул, например, поток управления, конструкторы обещаний, API обещаний, микрозадачи и многое другое, о чем вы можете узнать по ссылкам, которые я предоставил вам в дальнейшем. раздел чтения ниже.

Дальнейшие чтения