Ключевые слова Async/Await в JavaScript для простого асинхронного кода

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

Обратите внимание: чтобы понять, как работает async / await, нужно знать, как работают промисы.

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

Предположим, что вам нужно выполнить три асинхронных операции в следующей последовательности:

  1. Выберите пользователя из базы данных.
  2. Получить услуги пользователя из API.
  3. Рассчитать стоимость услуги на основе услуг с сервера.

Следующие функции иллюстрируют три задачи. Обратите внимание, что мы используем функцию setTimeout() для имитации асинхронной операции:

function getUser(userId, callback) {
    console.log('Get user from the database.');
    setTimeout(() => {
        callback({
            userId: userId,
            username: 'john'
        });
    }, 1000);
}

function getServices(user, callback) {
    console.log(`Get services of  ${user.username} from the API.`);
    setTimeout(() => {
        callback(['Email', 'VPN', 'CDN']);
    }, 2 * 1000);
}

function getServiceCost(services, callback) {
    console.log(`Calculate service costs of ${services}.`);
    setTimeout(() => {
        callback(services.length * 100);
    }, 3 * 1000);
}

Ниже показаны вложенные функции обратного вызова:

getUser(100, (user) => {
    getServices(user, (services) => {
        getServiceCost(services, (cost) => {
            console.log(`The service cost is ${cost}`);
        });
    });
});

Выход:

Get user from the database.
Get services of  john from the API.
Calculate service costs of Email,VPN,CDN.
The service cost is 300

Чтобы избежать этой проблемы ада обратных вызовов, ES6 представила промисы, которые позволяют вам писать асинхронный код более управляемыми способами.

Во-первых, вам нужно вернуть Promise в каждой функции:

function getUser(userId) {
    return new Promise((resolve, reject) => {
        console.log('Get user from the database.');
        setTimeout(() => {
            resolve({
                userId: userId,
                username: 'john'
            });
        }, 1000);
    })
}

function getServices(user) {
    return new Promise((resolve, reject) => {
        console.log(`Get services of  ${user.username} from the API.`);
        setTimeout(() => {
            resolve(['Email', 'VPN', 'CDN']);
        }, 2 * 1000);
    });
}

function getServiceCost(services) {
    return new Promise((resolve, reject) => {
        console.log(`Calculate service costs of ${services}.`);
        setTimeout(() => {
            resolve(services.length * 100);
        }, 3 * 1000);
    });
}

Затем вы связываете обещания:

getUser(100)
    .then(getServices)
    .then(getServiceCost)
    .then(console.log);

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

Если функция возвращает обещание, вы можете поместить ключевое слово await перед вызовом функции, например:

let result = await f();

Ожидание будет await, пока Promise, возвращенный из f(), будет установлен. Ключевое слово await можно использовать только внутри async функций.

Ниже определяется async функция, которая последовательно вызывает три асинхронных операции:

async function showServiceCost() {
    let user = await getUser(100);
    let services = await getServices(user);
    let cost = await getServiceCost(services);
    console.log(`The service cost is ${cost}`);
}

showServiceCost();

Как видите, асинхронный код теперь выглядит как синхронный.

Давайте углубимся в ключевые слова async/await.

Ключевое слово async

Ключевое слово async позволяет определить функцию, которая обрабатывает асинхронные операции.

Чтобы определить функцию, вы помещаете ключевое слово async перед ключевым словом function следующим образом:

async function sayHi() {
    return 'Hi';
}

Асинхронные функции выполняются асинхронно через цикл обработки событий. Он всегда возвращает Promise.

В этом примере, поскольку sayHi() возвращает Promise, вы можете использовать его следующим образом:

sayHi().then(console.log);

Вы также можете явно вернуть Promise из функции sayHi(), как показано в следующем коде:

async function sayHi() {
    return Promise.resolve('Hi');
}

Эффект тот же.

Помимо обычных функций, вы можете использовать ключевое слово async в выражениях функций:

let sayHi = async function () {
    return 'Hi';
}

функции стрелки:

let sayHi = async() => 'Hi';

Методы занятий:

class Greeter {
    async sayHi() {
        return 'Hi';
    }
}

Ключевое слово await

Вы используете ключевое слово await, чтобы дождаться, пока Promise не установится либо в разрешенном, либо в отклоненном состоянии. И вы можете использовать ключевое слово await только внутри async функции:

async function display() {
    let result = await sayHi();
    console.log(result);
}

В этом примере ключевое слово await указывает движку JavaScript дождаться завершения функции sayHi() перед отображением сообщения.

Обратите внимание, что если вы используете оператор await вне async функции, вы получите сообщение об ошибке.

Обработка ошибок

Если обещание разрешается, await promise возвращает результат. Однако, когда обещание отклоняется, await promise выдаст ошибку, как если бы было выражение throw.

Следующий код:

async function getUser(userId) {
     await Promise.reject(new Error('Invalid User Id'));
}

То же самое, что и это:

async function getUser(userId) {
    throw new Error('Invalid User Id');
}

В реальном сценарии обещание выдаст ошибку через некоторое время.

Вы можете найти ошибку, используя оператор try...catch, так же, как и обычный оператор throw :

async function getUser(userId) {
    try {
       const user = await Promise.reject(new Error('Invalid User Id'));
    } catch(error) {
       console.log(error);
    }
}

Можно найти ошибки, вызванные одним или несколькими await promise :

async function showServiceCost() {
    try {
       let user = await getUser(100);
       let services = await getServices(user);
       let cost = await getServiceCost(services);
       console.log(`The service cost is ${cost}`);
    } catch(error) {
       console.log(error);
    }
}
Рейтинг
( Пока оценок нет )
Александр Русаков / автор статьи
Программист, разработчик, 12 лет опыта работы в крупных компаниях. Быстро освоил typescript, делюсь своими знаниями на страницах этого сайта.
Загрузка ...
JavaScript и TypeScript