Что такое 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).