Статьи

Обратные вызовы JavaScript, обещания и асинхронные функции: часть 2

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

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

  • обещания
  • Асинхронные функции
  • Рассмотрение
  • Ресурсы

Давайте посмотрим, что такое обещание концептуально. Представьте себе сценарий, когда вы совершаете покупки в Интернете и покупаете пару обуви. Когда вы выезжаете, вам по электронной почте выдается сводка вашей покупки.

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

В коде говорят, обещание — это объект, который гарантирует, что мы получим будущее значение для нашего запроса, будет ли он успешным или нет. Это общая форма для создания и использования обещания:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function task1() {
    return new Promise(function(resolve, reject) {
        resolve(data);
        reject(error);
    });
}
 
task1()
.then(function(result){
    console.log(result);
})
.catch(function(error){
    console.log(error);
});

Чтобы создать обещание, вы создаете экземпляр объекта обещания и пишете свой асинхронный код внутри функции обратного вызова обещания. Данные, которые вы хотите вернуть из обещания, передаются в качестве аргумента функции resolve , а ваше сообщение об ошибке передается в функцию reject . Мы связываем вместе обещания, используя метод then . Это позволяет нам выполнять задачи последовательно.

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

В первой части этого руководства мы использовали обратные вызовы, чтобы открыть файл и получить сообщение и его комментарии. Вот как выглядит полный код с использованием обещаний:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const fs = require(‘fs’);
const path = require(‘path’);
const postsUrl = path.join(__dirname, ‘db/posts.json’);
const commentsUrl = path.join(__dirname, ‘db/comments.json’);
 
//return the data from our file
function loadCollection(url) {
    return new Promise(function(resolve, reject) {
        fs.readFile(url, ‘utf8’, function(error, data) {
            if (error) {
                reject(‘error’);
            } else {
                resolve(JSON.parse(data));
            }
        });
    });
}
 
//return an object by id
function getRecord(collection, id) {
    return new Promise(function(resolve, reject) {
        const data = collection.find(function(element){
            return element.id == id;
        });
 
        resolve(data);
    });
}
 
//return an array of comments for a post
function getCommentsByPost(comments, postId) {
    return comments.filter(function(comment){
        return comment.postId == postId;
    });
}
 
//initialization code
loadCollection(postsUrl)
.then(function(posts){
    return getRecord(posts, «001»);
})
.then(function(post){
    console.log(post);
    return loadCollection(commentsUrl);
})
.then(function(comments){
    const postComments = getCommentsByPost(comments, «001»);
    console.log(postComments);
})
.catch(function(error){
    console.log(error);
});

Разница здесь в том, что наш метод открытия файла теперь обернут в объект обещания. И вместо того, чтобы вкладывать наши задачи в обратные вызовы, они соединяются вместе с then .

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

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

С обещаниями мы вернем что-то перед тем, как перейти к следующему заданию. Если нам нужно перенести это в следующее продолжение нашего кода, мы можем использовать оператор then .

Используя обещания, напишите программу, которая откроет файл пользователей, получит информацию о пользователе, а затем откроет файл сообщений и распечатает все сообщения пользователя.

Обещания улучшают дизайн нашей программы, но мы можем добиться большего. Было бы очень удобно, если бы мы могли выполнять наши задачи синхронно следующим образом:

1
2
3
task1();
task2();
task3();

Ну, мы можем с шаблоном async / await. Для этого мы начнем с обертывания наших задач в асинхронную функцию. Эта функция возвращает обещание. Затем мы реализуем обработку ошибок, заключая наши задачи в оператор try/catch .

Если обещание выполнено, оно выполнит все задачи, которые были внутри нашего блока try . Если он отклонен, блок catch будет выполнен. Добавление ключевого слова await до того, как любая задача приостановит нашу программу, пока задача не завершится.

Это общая форма для использования асинхронных функций:

01
02
03
04
05
06
07
08
09
10
11
12
async function initTasks () {
    try {
        const a = await task1();
        const b = await task2();
        const c = await task3();
        //do something with a, b, and c
    } catch (error) {
        //do something with the error object
    }
}
 
initTasks();

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
async function getPost(){
    try {
        const posts = await loadCollection(postsUrl);
        const post = await getRecord(posts, «001»);
        const comments = await loadCollection(commentsUrl);
        const postComments = await getCommentsByPost(comments, post.id);
         
        console.log(post);
        console.log(postComments);
    } catch (error) {
        console.log(error);
    }
}
 
getPost();

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

Один из примеров использования этого шаблона — когда у нас есть код очистки, который нам нужно выполнить. Этот код не обязательно должен содержаться в блоке finally . Он может быть записан после оператора catch и будет выполнен. Обещания не имеют встроенного синтаксиса. Чтобы достичь того же эффекта, нам нужно было бы связать другой оператор then после нашего оператора catch .

Используя async / await, напишите программу, которая откроет файл пользователей, получит информацию о пользователе, а затем откроет файл сообщений и распечатает информацию пользователя и все его сообщения.

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

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