Что такое Promise? Для чего он нужен?
Promise
в JavaScript это специальный объект, который связан с выполнением асинхронной операции и может находиться в одном из трёх состояний:
pending
(ожидание) - начальное состояние, не выполнено и не отклонено.fulfilled
(выполнено успешно) - операция выполнена успешно.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
:
.then
- используется для присоединения обработчиков к Promise и возвращает новый Promise, решающийся до исходного значения. Он принимает два аргумента:callback
успеха иcallback
ошибки. Если возвращаемый коллбэк из.then
возвращает значения, они становятся аргументами следующего.then
в цепочке промисов..catch
- обрабатывает только отклоненные промисы..finally
- выполняется в конце промисов, независимо от того, был ли промис выполнен успешно или отклонен.
Что такое цепочка промисов?
Это серия операций с промисами, которые выполняются последовательно. Каждый следующий промис начинает выполнение после того, как предыдущий промис был успешно выполнен или отклонен.
promise.then(result => {
// обработка результата
return anotherAsyncOperation(result);
}).then(anotherResult => {
// обработка результата другой асинхронной операции
}).catch(error => {
// обработка ошибки
});
Что могут возвращать их коллбеки?
Обработчики коллбэков в промисе (т.е. коллбэки, которые передаются в .then
, .catch
, .finally
), могут возвращать следующие значения:
- Обычное значение (не промис): Если обработчик возвращает обычное значение, это значение становится результатом промиса возвращаемого методом
.then
.
Promise.resolve(1)
.then((value) => {
console.log(value); // 1
return value * 2;
})
.then((newValue) => {
console.log(newValue); // 2
});
- Другой промис: Если обработчик возвращает промис, состояние возвращаемого промиса будет зависеть от этого промиса. Если возвращаемый промис разрешен, следующий в цепочке
.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' });
- Если обработчик возвращает другой промис: Новый промис, возвращаемый из
.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'
});
- Если обработчик бросает исключение: Новый промис, возвращаемый из
.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'
В чем преимущества и недостатки коллбэков и промисов?
Коллбеки
Преимущества:
- Простота использования: для осуществления асинхронного кода достаточно передать функцию в качестве коллбека.
- Широкая поддержка: коллбеки работают в любых версиях JavaScript и во всех браузерах.
Недостатки:
- Callback Hell: если асинхронные операции должны выполняться последовательно, это может привести к вложенности коллбеков и сделать код трудным для чтения и отладки. Это явление обычно называют "callback hell" или "pyramid of doom".
- Обработка ошибок: стандартный подход к обработке ошибок try/catch не работает с асинхронными коллбеками. Обычно ошибки передаются как первый аргумент каждого коллбека, что требует от цепочки коллбеков проверять ошибки на каждом шаге.
Промисы
Преимущества:
- Цепочка промисов: Промисы могут быть объединены в цепочки, которые более понятны и удобны для чтения по сравнению с коллбеками.
- Обработка ошибок: промисы имеют встроенные методы then, catch и finally для обработки успешных и неудачных операций. С промисами можно использовать стандартный подход к обработке ошибок с помощью try/catch. 3. Возможность использования с async/await: это делает код еще более читаемым и понятным. 4. Промис будет выполнен только один раз: он либо будет выполнен (resolved), либо отклонен (rejected), в отличие от коллбеков, которые могут быть вызваны многократно.
Недостатки:
- Большее количество кода: создание промиса требует больше кода, чем использование коллбека.
- Сложность: промисы сложнее понять и использовать для новичков.
- Нет отмены: 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)
.