Promise

Что такое Promise? Для чего он нужен?

Promise в JavaScript это специальный объект, который связан с выполнением асинхронной операции и может находиться в одном из трёх состояний:

  1. pending (ожидание) - начальное состояние, не выполнено и не отклонено.
  2. fulfilled (выполнено успешно) - операция выполнена успешно.
  3. rejected (выполнено с ошибкой) - операция завершилась с ошибкой. Promise используется для управления асинхронными операциями, такими как выполнение HTTP-запроса или чтение файла из системы. Они позволяют упростить написание асинхронного кода и делают его более читаемым.

Как создать Promise?

Чтобы создать промис, нужно использовать конструктор Promise:

let promise = new Promise((resolve, reject) => {
// асинхронная операция
});

Здесь resolve и reject - это функции.

Так же можно создавать без констурктора:

let resolvedPromise = Promise.resolve(123);
let rejectedPromise = Promise.reject(new Error("Ошибка"));

Когда выполняется коллбэк, передаваемый аргументом в конструктор промиса?

Коллбэк, передаваемый в конструктор Promise, называется исполнительной функцией (executor function) и выполняется сразу же при вызове конструктора new Promise(executor).

Однако, это не означает, что обещание (промис) будет немедленно выполнено или отклонено – эта функция обычно используется для запуска асинхронной операции, которая в какой-то момент в будущем, когда операция будет завершена или завершится с ошибкой, вызовет resolve или reject соответственно, меняя состояние промиса.

console.log("before promise");
let promise = new Promise((resolve, reject) => {
    console.log("inside promise");
    setTimeout(() => {
        resolve("promise resolved");
    }, 1000);
});
console.log("after promise");

Выполнение этого кода даст следующий вывод:

before promise
inside promise
after promise

Это подтверждает, что функция-исполнитель выполняется немедленно, даже до того, как конструктор возвращает созданный промис. Фактическое разрешение промиса, однако, задерживается до момента вызова resolve или reject.

Методы then, catch, finally

Promise методы - then, catch, finally:

  1. .then - используется для присоединения обработчиков к Promise и возвращает новый Promise, решающийся до исходного значения. Он принимает два аргумента: callback успеха и callback ошибки. Если возвращаемый коллбэк из .then возвращает значения, они становятся аргументами следующего .then в цепочке промисов.
  2. .catch - обрабатывает только отклоненные промисы.
  3. .finally - выполняется в конце промисов, независимо от того, был ли промис выполнен успешно или отклонен.

Что такое цепочка промисов?

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

promise.then(result => {
// обработка результата
    return anotherAsyncOperation(result);
    }).then(anotherResult => {
    // обработка результата другой асинхронной операции
    }).catch(error => {
    // обработка ошибки
});

Что могут возвращать их коллбеки?

Обработчики коллбэков в промисе (т.е. коллбэки, которые передаются в .then, .catch, .finally), могут возвращать следующие значения:

  1. Обычное значение (не промис): Если обработчик возвращает обычное значение, это значение становится результатом промиса возвращаемого методом .then.
Promise.resolve(1)
.then((value) => {
    console.log(value);  // 1
    return value * 2;
})
.then((newValue) => {
    console.log(newValue);  // 2
});
  1. Другой промис: Если обработчик возвращает промис, состояние возвращаемого промиса будет зависеть от этого промиса. Если возвращаемый промис разрешен, следующий в цепочке .then запустится с этим разрешенным значением. Если возвращаемый промис отклонен, следующий .catch в цепочке будет выполнен.
let promise1 = Promise.resolve(1);
let promise2 = Promise.resolve(2);
 
promise1
.then((value) => {
    console.log(value);  // 1
    return promise2;
})
.then((valueFromPromise2) => {
    console.log(valueFromPromise2);  // 2
});

Как возвращаемое значение влияет на состояние промиса, возвращаемого данным методом?

Возвращаемое значение из .then(), .catch() или .finally() обработчиков влияет на состояние промиса следующим образом: 1. Если обработчик возвращает значение (не промис): Новый промис, возвращаемый из .then(), .catch() или .finally(), будет разрешен этим значением. javascript Promise.resolve() .then(() => { return 'value'; }) .then((value) => { console.log(value); // 'value' });

  1. Если обработчик возвращает другой промис: Новый промис, возвращаемый из .then(), .catch() или .finally(), будет иметь то же состояние (разрешено/отклонено), что и возвращаемый промис.
let anotherPromise = new Promise((resolve, reject) => {
setTimeout(() => {
    resolve("anotherPromise value");
}, 1000);
});
 
Promise.resolve()
.then(() => {
    return anotherPromise;
})
.then((value) => {
    console.log(value);  // 'anotherPromise value'
});
  1. Если обработчик бросает исключение: Новый промис, возвращаемый из .then(), .catch() или .finally(), будет отклонен с этой ошибкой.
Promise.resolve().then(() => {
    throw new Error('Error');
})
.catch(error => {
    console.log(error.message);  // 'Error'
});

Для чего нужны и как использовать следующие методы:

Promise.resolve

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

Promise.resolve('Resolved!').then(console.log) // 'Resolved!'

Promise.reject

Этот метод возвращает Promise объект, который завершен неудачей с переданным причиной. Это полезно, когда вы заранее знаете, что промис будет отклонен.

Promise.reject(new Error('Rejected!')).catch(error => console.log(error)) // Error: 'Rejected!'

Promise.all

Этот метод возвращает Promise объект, который выполняется успешно, когда все промисы в итерируемой коллекции успешно завершены или отклоняются, если хоть один из промисов не удается. Возвращаемое значение – массив значений, соответствующий порядку промисов в итерируемой коллекции.

Promise.all([Promise.resolve('one'),Promise.resolve('two')])
    .then(console.log) // ['one', 'two']

Promise.allSettled

Этот метод возвращает Promise объект, который выполняется, когда все промисы в итерируемой коллекции завершились (не важно успешно или с ошибкой). Возвращает массив объектов с результами каждого промиса.

Promise.allSettled([Promise.resolve('one'), Promise.reject('two')])
    .then(console.log)
// [{status: 'fulfilled', value: 'one'}, {status: 'rejected', reason: 'two'}]

Promise.race

Принимает итерируемую коллекцию промисов и возвращает промис, который выполняется или отклоняется с тем же значением или причиной, как только выполняется или отклоняется первый промис в итерируемой коллекции.

Promise.race([Promise.resolve('one'),Promise.resolve('two')])
    .then(console.log) // 'one'

Promise.any

Принимает итерируемую коллекцию промисов и, как только первый промис в коллекции выполняется, возвращает промис, который выполняется с этим значением. Если все промисы отклоняются, то возвращает ошибка с типом AggregateError.

Promise.any([Promise.reject('one'), Promise.resolve('two')]).then(console.log) // 'two'

В чем преимущества и недостатки коллбэков и промисов?

Коллбеки

Преимущества:

  1. Простота использования: для осуществления асинхронного кода достаточно передать функцию в качестве коллбека.
  2. Широкая поддержка: коллбеки работают в любых версиях JavaScript и во всех браузерах.

Недостатки:

  1. Callback Hell: если асинхронные операции должны выполняться последовательно, это может привести к вложенности коллбеков и сделать код трудным для чтения и отладки. Это явление обычно называют "callback hell" или "pyramid of doom".
  2. Обработка ошибок: стандартный подход к обработке ошибок try/catch не работает с асинхронными коллбеками. Обычно ошибки передаются как первый аргумент каждого коллбека, что требует от цепочки коллбеков проверять ошибки на каждом шаге.

Промисы

Преимущества:

  1. Цепочка промисов: Промисы могут быть объединены в цепочки, которые более понятны и удобны для чтения по сравнению с коллбеками.
  2. Обработка ошибок: промисы имеют встроенные методы then, catch и finally для обработки успешных и неудачных операций. С промисами можно использовать стандартный подход к обработке ошибок с помощью try/catch. 3. Возможность использования с async/await: это делает код еще более читаемым и понятным. 4. Промис будет выполнен только один раз: он либо будет выполнен (resolved), либо отклонен (rejected), в отличие от коллбеков, которые могут быть вызваны многократно.

Недостатки:

  1. Большее количество кода: создание промиса требует больше кода, чем использование коллбека.
  2. Сложность: промисы сложнее понять и использовать для новичков.
  3. Нет отмены: JavaScript промисы не могут быть отменены после их создания. Когда промис стартован, он должен быть выполнен или отклонен, но не может быть остановлен как процесс. Этот недостаток пытаются адресовать с помощью AbortController, но это всё еще слишком сложно и неудобно.

Какое состояние и результат будут у следующего промиса и почему:

const promise = new Promise((resolve, reject) => {
  resolve(0);
  reject(1);
  resolve(2);
});

Состояние этого промиса будет fulfilled (выполнен), а результат будет равен 0.

Это происходит потому, что состояние промиса может быть изменено только один раз. Перехода из состояния fulfilled или rejected к другому не происходит. В приведенном коде мы вызываем resolve(0), что устанавливает состояние промиса в fulfilled и его результат в 0. Последующие вызовы reject(1) и resolve(2) не влияют на состояние или результат промиса, так как его состояние уже было установлено первым вызовом resolve(0).