Что такое Promise в JavaScript

год назад·6 мин. на чтение

Обещания (Promises) — это мощная возможность JavaScript, которая упрощает понимание асинхронных операций и улучшает читаемость кода.

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

Что такое Promises в JavaScript?

Успех (или сбой) асинхронной операции и значение, которое она генерирует, представлены объектом, называемым обещанием. Вместо вложения обратных вызовов он позволяет нам обрабатывать асинхронный код более элегантным и организованным способом с помощью цепочек методов. Обещания могут находиться в одном из трех состояний: pending (ожидание), fulfilled (выполнен) или rejected (отклонен). Pending - начальное состояние Promise, которое означает, что асинхронная операция все еще выполняется. Если операция выполнена успешно, Promise переходит в состояние “выполнен”, а при возникновении проблемы — в состояние “отклонен”.

Этапы жизненного цикла Promise

Давайте подробно рассмотрим каждый этап жизненного цикла Promise на примерах кода:

Ожидание

Объект Promise находится в состоянии ожидания на момент создания. На данный момент асинхронная операция все еще продолжается, и обещание не принимается и не отклоняется. Вот пример:
const promise = new Promise((resolve, reject) => {
  // Асинхронная операция, например, запрос данных через API,
  // resolve(result) или reject(error) будут вызваны позднее
});

Выполнено (Fulfilled)

Обещание переходит в состояние “выполнено”, как только асинхронная операция успешно завершена. На этом этапе связанное значение (результат) становится доступным. Для обработки выполненного обещания мы используем метод .then() Вот пример:
const promise = new Promise((resolve, reject) => {
  // Симуляция асинхронной операции
  setTimeout(() => {
    resolve("Operation succeeded!");
  }, 2000);
});

promise.then((result) => {
  console.log(result); // Вывод: "Operation succeeded!"
});

Отклонено (Rejected)

В случае, если возникает проблема с асинхронной операцией, Promise переходит в отклоненное состояние. Оно обозначает, что операция не удалась, и предоставляет объекту ошибки соответствующую информацию. Для обработки отклоненного Promise мы используем метод .catch() Вот пример:
const promise = new Promise((resolve, reject) => {
  // Симуляция асинхронной операции
  setTimeout(() => {
    reject(new Error("Something went wrong!"));
  }, 2000);
});

promise.catch((error) => {
  console.log(error.message); // Вывод: "Something went wrong!"
});

Цепочка обещаний

Обещания имеют ряд важных преимуществ, в том числе возможность объединять несколько асинхронных операций, что улучшает читаемость кода. Мы достигаем этого, используя метод .then() для возврата нового Promise. Вот пример:
const getUser = () => {
  return new Promise((resolve, reject) => {
    // Симуляция асинхронной операции
    setTimeout(() => {
      resolve({ id: 1, name: "John" });
    }, 2000);
  });
};

const getUserPosts = (user) => {
  return new Promise((resolve, reject) => {
    // Симуляция асинхронной операции
    setTimeout(() => {
      resolve(["Post 1", "Post 2"]);
    }, 2000);
 });
};

getUser()
  .then((user) => getUserPosts(user))
  .then((posts) => console.log(posts)); // Вывод: ["Post 1", "Post 2"]

Вспомогательные функции для объектов Promise

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

Promise.all()

Когда все объекты Promise во входном массиве выполнены, эта функция возвращает новый объект Promise. Вот пример:
const fetchUser = () => {
  return new Promise((resolve, reject) => {
    // Симуляция асинхронного вызова API
    setTimeout(() => {
      const user = { id: 1, name: "John" };
      resolve(user);
    }, 2000);
  });
};

const fetchPosts = () => {
  return new Promise((resolve, reject) => {
    // Симуляция асинхронного вызова API
    setTimeout(() => {
      const posts = ["Post 1", "Post 2"];
      resolve(posts);
    }, 1500);
  });
};

const fetchComments = () => {
  return new Promise((resolve, reject) => {
    // Симуляция асинхронного вызова API
    setTimeout(() => {
      const comments = ["Comment 1", "Comment 2"];
      resolve(comments);
    }, 1000);
  });
};

Promise.all([fetchUser(), fetchPosts(), fetchComments()])
  .then(([user, posts, comments]) => {
    console.log("User:", user);
    console.log("Posts:", posts);
    console.log("Comments:", comments);
  })
  .catch((error) => {
    console.log("Error:", error);
  });
Три функции fetchUser(), fetchPosts() и fetchComments() включены в приведенный выше пример. Для пользовательских данных, пользовательских сообщений и комментариев пользователей каждая функция имитирует асинхронный вызов API, возвращая Promise. Передавая массив Promise ([fetchUser(), fetchPosts(), fetchComments()]) в Promise.all(), мы создаем новый Promise, который выполняется после успешного выполнения каждого Promise в массиве. При обработке выполнения метод .then() применяет синтаксис, деструктурирующий массив, для получения разрешенных значений каждого объекта Promise. Когда в этой ситуации успешно выполняются все обещания, деструктурирование массива присваивает значения fetchUser(), fetchPosts() и fetchComments() переменным user, posts и comments соответственно. Пользователь, публикации и комментарии выводятся в консоль. Если какой-либо из Promises не удался вызывается .catch() и ошибка выводится в консоль. Promise.all() позволяет нам эффективно извлекать несколько асинхронных ресурсов и обрабатывать их все одновременно после успешного завершения каждого запроса.

Promise.race()

Как только какой-либо из объектов Promise во входном массиве выполнится, эта функция возвращает новый объект Promise, который либо выполняется, либо отклоняется. Вот пример:
const fetchResource = (resource, delay) => {
  return new Promise((resolve, reject) => {
    // Симуляция асинхронного вызова API
    setTimeout(() => {
      resolve(`${resource} is fetched successfully in ${delay}ms`);
    }, delay);
  });
};

const resource1 = fetchResource("Resource 1", 2000);
const resource2 = fetchResource("Resource 2", 1500);
const resource3 = fetchResource("Resource 3", 1000);

Promise.race([resource1, resource2, resource3])
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.log(error);
  });
В приведенном выше примере есть три функции, называемые fetchResource() которые имитируют асинхронные вызовы API, возвращая Promises. Каждый вызов имитирует время, необходимое для получения определенного ресурса, с разным временем задержки. Когда массив Promises ([resource1, resource2, resource3]) передается методу Promise.race(), создается новый Promise, который выполняется (выполняется успешно или отклоняется) в ответ на любое Promise в массиве Promise.race(). Значение успешного Promise передается в качестве параметра result и выводится в консоли в методе .then(), который используется для обработки выполнения. В этом случае победителем будет считаться тот ресурс, который разрешится первым (т.е. тот, у которого наименьшая задержка), а его значение будет выведено на консоль. Если какой-либо из Promises не удался, то вызывается .catch() и ошибка выводится в консоли. Мы можем выполнить несколько асинхронных операций одновременно и отреагировать на результат самой быстрой с помощью метода Promise.race(). Это полезно в ситуациях, когда мы хотим действовать в соответствии с первоначальным ответом или завершением.

Promise.resolve() и Promise.reject()

Без необходимости дополнительных асинхронных операций эти функции позволяют создавать Promise, которые уже были выполнены или отклонены соответственно. Благодаря мощным возможностям, которые эти вспомогательные функции предлагают для манипулирования и управления Promises, асинхронное программирование JavaScript теперь более адаптируемо и выразительно. Promise.resolve() и Promise.reject() используются в следующем примере кода:
const fetchData = (shouldSucceed) => {
  if (shouldSucceed) {
    return Promise.resolve("Data fetched successfully");
  } else {
    return Promise.reject(new Error("Failed to fetch data"));
  }
};

fetchData(true)
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.log(error);
  });

fetchData(false)
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.log(error);
  });
Функция fetchData() в приведенном выше примере имеет параметр shouldSucceed, который указывает, должна ли выборка данных быть успешной или неудачной. Promise.resolve() используется для создания и возврата объекта Promise, который немедленно выполняется с сообщением «Data fetched successfully», если shouldSucceed имеет значение true. Promise.reject() используется для создания и возврата объекта Promise, который немедленно отклоняется с новым объектом Error и сообщением «Failed to fetch data», если shouldSucceed имеет значение false. Возвращенное обещание выполняется в первом вызове fetchData() с shouldSucceed, равным true, и выполнение управляется методом .then(). В result передается значение «Data fetched successfully», которое затем выводится в консоль. Во втором вызове fetchData() с shouldSucceed, равным false, возвращенный объект Promise отклоняется, и для обработки отклонения используется .catch(). Объект ошибки, содержащий сообщение «Failed to fetch data», передается в качестве параметра error и выводится в консоль. Используя Promise.resolve() и Promise.reject() мы можем легко создавать Promise, которые уже разрешены или отклонены, без необходимости дополнительных асинхронных операций. Это полезно при обработке синхронных значений или ошибок в виде объектов Promise.

Итоги

Асинхронное программирование JavaScript произвело революцию благодаря обещаниям, которые предлагают хорошо организованный и красивый способ решения трудоемких задач. Обещания помогают нам создать более удобочитаемый и поддерживаемый код. Определение объектов Promise, этапы их жизненного цикла и вспомогательные функции, таких как Promise.all(), Promise.race(), Promise.resolve() и Promise.reject() были рассмотрены в этом обширном руководстве. Мы можем эффективно обрабатывать асинхронные операции и изящно обрабатывать сценарии успеха и ошибок, понимая этапы жизненного цикла - pending, fulfilled и rejected. Разработчики могут создавать надежные и эффективные приложения, которые легко справляются со сложными асинхронными задачами, используя Promises и их вспомогательные функции. Поток управления оптимизирован, а удобочитаемость кода улучшена за счет цепочек Promise. Кроме того, вспомогательные функции JavaScript улучшают обработку ошибок и упрощают выполнение многочисленных асинхронных операций. Современная веб-разработка требует использования асинхронного программирования, а знание обещаний позволяет разработчикам создавать код, который является более чистым и простым в обслуживании. Вы можете создавать надежные приложения, которые эффективно обрабатывают асинхронные операции, интегрируя Promises в свои JavaScript проекты и используя их вспомогательные функции.

Видео-курс о концепциях JavaScript

год назад·2 мин. на чтение

Рекурсия в JavaScript - Рекурсивные функции

Рекурсия это ситуация когда функция вызывает сама себя.

Каррирование в JavaScript

В этом видео разбираемся как каррировать функции в JavaScript, и пишем функцию для каррирования функций.

Итераторы в JavaScript

В этом выпуске говорим о том как сделать кастомный объект итерируемым с for...of. Итераторы или итерируемые объекты - это объекты, которые можно перебирать в цикле. Иногда бывает полезным создать собственный итератор на свои объекты, которые не являются итерируемыми изначально. Это будет полезно, т.к. удобство работы с такими объектами вырастет. Нужно будет всего лишь написать свой алгоритм перебора и возвращать результат определенного вида.
Чтобы сделать объект итерируемым нужно чтобы объект имел поле Symbol.iterator. Это поле должно быть функцией. Функция должна возвращать объект с полем next. next это функция, которая в свою очередь должна возвращать объект с полями value и done.

Для чего генераторы в JavaScript?

В этом выпуске рассматриваем генераторы в JavaScript. Генераторы в JavaScript (generators) - это особый тип функций, которые могут приостанавливать свое выполнение, выполнять результат, и далее возобновлять свою работу в произвольный момент времени, вернуть еще один результат и т.д. При вызове метода next, генератор возобновляет выполнение и при достижении yield приостанавливается. Результат выполнения функции генератора это ничто иное как итератор. У него есть метод next, который возвращает объект с полями value и done.

Что такое прокси (Proxy) в JavaScript?

Proxy в JavaScript позволяет перехватывать и переопределять операции над объектом.

Что такое поднятие (hoisting) в JavaScript

Поднятие - это механизм который делает возможным использование функций и переменных до их объявления.

var, let, const в JavaScript

Функции высшего порядка в JavaScript