Promises в javascript
Основы promises в javascript
Вместо введения
Код javascript
выполняется в одном потоке, синхронно строчка за строчкой. Такой код выполняется сразу. Пока выполняется текущая строка следующая ждет выполнения, то есть дальнейший код блокируется.
Чтобы это обойти нужно запустить код асинхронно, это все те операции для выполнения которых нужно некоторое время. Они всегда выполняются после синхронных операции. Асинхронные задачи выполняются по очереди, которой управляет движок javascript
.
Важно заметить, поток выполнения один, движок javascript
сам управляет задачами в очереди на выполнение.
Promise
Promise
- Объект для работы с асинхронным кодом. Этот же объект возвращается в качестве результата.
Сегодня разберем основы Promise
.
Сам объект может быть в одном из 3 состояний:
pending
- ожидание, по умолчаниюfulfilled
- выполнено успешно, результат полученrejected
- выполнено с ошибкой
Если просто создать Promise
на ровном месте получим ошибку.
1
let promise = new Promise(); // Uncaught TypeError: undefined is not a function
Так, как нужно передать в параметры функцию executor
, хорошо.
Задача функции
executor
перевести promise в состояниеfulfilled
илиrejected
.
1
2
// Получаем состояние ожидания
let promise = new Promise(() => {}); // Promise { <state>: "pending" }
Причем функция выполниться, еще до создания объекта Promise
. Что иллюстрирует пример.
1
2
3
4
5
6
let promise = new Promise(() => {console.log(123)}); // 123
// Либо вынести в отдельную функцию
function executor() {
console.log(123);
}
let promise = new Promise(executor); // 123
Но, если вернуть значения из функции executor
, оно не будет возвращено.
1
let promise = new Promise(() => {return 123;}); // ...
В параметры функции executor
передаются еще две функции resolve
и reject
. Они как раз меняют состояние Promise
.
Название функции может быть любым просто устоялось именно такое наименование.
То есть синтаксис Promise
такой new Promise(function(resolve, reject) { ... });
1
2
3
// resolve, reject это функции которые предоставляет promise
// Напомню вся эта функция (resolve, reject) => {} вызвается сразу же.
let promise = new Promise((resolve, reject) => {});
Нам нужно самим решить, что успешно, а что нет. Разрешим Promise
.
1
2
3
let promise = new Promise((resolve, reject) => {
resolve();
}); // Promise { <state>: "fulfilled", <value>: undefined }
Как видим состояние поменялось на fulfilled
.
В resolve
можно передать данные асинхронной операции, но это не обязательно.
1
2
3
let promise = new Promise((resolve, reject) => {
resolve( 'data'); // Promise { <state>: "fulfilled", <value>: "data" }
});
Можно сказать, что Promise
завершен успешно.
Теперь представим произошла ошибка, переведем Promise
в состояние rejected
.
1
2
3
let promise = new Promise((resolve, reject) => {
reject();
}); // Promise { <state>: "rejected", <reason>: undefined } Как раз вот и есть ошибка Uncaught (in promise) undefined
Обработаем ошибку передав туда сообщение.
1
2
3
let promise = new Promise((resolve, reject) => {
reject(new Error('error'));
}); // Uncaught (in promise) Error: error
Теперь вызовем resolve
несколько раз.
1
2
3
4
5
let promise = new Promise((resolve, reject) => {
resolve('1'); // Будет вызван только этот resolve, остальные игнорируются
resolve('2');
resolve('3');
}); // Promise { <state>: "fulfilled", <value>: "1" }
Или тоже, самое с reject
.
1
2
3
4
5
6
let promise = new Promise((resolve, reject) => {
reject('error'); // Выполнение только тут
resolve('resolve');
reject('error2');
resolve('resolve2');
}); // Promise { <state>: "rejected", <reason>: "error" } Uncaught (in promise) error
Это говорит о том, что состояние Promise
можно менять только один раз.
Promise
может изменить свое состояние только один раз.
Еще есть возможность сразу же разрешить или нет Promise
“на месте” с использованием статических методов.
1
2
let resolve = Promise.resolve('resolve'); // Promise { <state>: "fulfilled", <value>: "resolve" }
let reject = Promise.reject('reject'); // Promise { <state>: "rejected", <reason>: "reject" }
Эмулируем разное поведение с рандомным результатом.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('ок');
} else {
reject('error');
}
},2000)
});
// Обработка результата, но об этом подробнее ниже.
promise
.then((success) => {console.log(success)})
.catch((error) => {console.log(error)});
Если обновлять страницу результат будет случайным каждый раз.
Обработка результатов
Когда состояние Promise
меняется, его можно обработать с помощью методов:
then()
- вызван тогда когда будет вызванresolve()
, но с оговорками.catch()
- вызван тогда когда будет вызванreject()
.finally()
- будет вызван когда состояние поменялось на"fulfilled"
или"rejected"
Promise
позволяет получить результат в будущем.
Рассмотрим ситуацию когда результат попадет в finally
, когда Promise
будет разрешен или отклонен, говорят еще это состояние settled
.
1
2
3
4
5
6
7
8
9
10
let promise = new Promise((resolve, reject) => {
resolve();
});
let promise2 = new Promise((resolve, reject) => {
reject();
});
promise.finally(() => {console.log('final - resolve')}); // final - resolve
promise2.finally(() => {console.log('final - reject')}); // final - reject
Теперь разрешим Promise
успешно и получим закономерный результат с помощью then
.
1
2
3
4
5
6
let promise = new Promise((resolve, reject) => {
resolve('success');
});
promise
.then((data) => {console.log(data)}) //success
Метод then
возвращает Promise
с двумя callbacks
onFulfilled
- значение с которымPromise
был выполнен.onRejected
- значение с которымPromise
был отклонен.
1
2
3
4
5
6
7
8
9
10
let promise = new Promise((resolve, reject) => {
reject('error'); // Выполнен этот callback
resolve('success');
});
promise
.then(
(data) => {console.log(data)},
(data) => {console.log(data)} // Попадем сюда, error
)
Но такой, код менее очевиден, гораздо нагляднее обработать это с помощью catch()
. Думаю делать лучше так.
1
2
3
4
5
let promise = new Promise((resolve, reject) => {
reject('error');
});
promise.catch((data) => {console.log(data)}) // error
catch()
ловит все ошибки.
Но ошибки могут возникнуть в самом then
, но только если она возникает во втором параметре onRejected
.
1
2
3
4
5
6
7
8
9
10
let promise = new Promise((resolve, reject) => {
reject('error');
});
promise
.then((data) => {
},() => {
// Допустим здесь произошла ошибка
throw new Error('error');
})
.catch((error) => {console.log(error.message)}) // error
Получается каждый then, catch
, возвращает новый Promise
всегда. Что показывает пример.
Новый
Promise
будет всегда возращен.
1
2
3
4
5
6
7
8
9
10
11
12
let promise = new Promise((resolve, reject) => {
resolve('success');
});
promise
.then((data) => {console.log(data); return data}) // success
.then((data) => {console.log(data); return data}) // success
.then((data) => {console.log(data); return data}) // success
.then((data) => {console.log(data); return data}) // success
.then((data) => {console.log(data); return data}) // success
.then((data) => {console.log(data); return data}) // success
.then((data) => {console.log(data); return data}) // success
К примеру результат можно видоизменять на каждой итерации.
1
2
3
4
5
6
7
8
9
10
11
12
let promise = new Promise((resolve, reject) => {
resolve(1);
});
promise
.then((data) => {console.log(data); return data + 1}) // 1
.then((data) => {console.log(data); return data + 1}) // 2
.then((data) => {console.log(data); return data + 1}) // 3
.then((data) => {console.log(data); return data + 1}) // 4
.then((data) => {console.log(data); return data + 1}) // 5
.then((data) => {console.log(data); return data + 1}) // 6
.then((data) => {console.log(data); return data + 1}) // 7
Или даже так, так как код выполняется асинхронно, все результаты будут выведены сразу же, спустя 3 секунды.
1
2
3
4
5
6
7
8
let promise = new Promise((resolve, reject) => {
resolve(1);
});
promise
.then((data) => {setTimeout(() => {console.log(data) },3000); return data + 1;}) // 1
.then((data) => {setTimeout(() => {console.log(data) },3000); return data + 1;}) // 2
.then((data) => {setTimeout(() => {console.log(data) },3000); return data + 1;}) // 3
Чтобы эмулировать таймер, увеличим время на 3 сек, в каждом then
.
1
2
3
4
5
6
7
8
let promise = new Promise((resolve, reject) => {
resolve(1);
});
promise
.then((data) => {setTimeout(() => {console.log(data) },3000); return data + 1;}) // 1
.then((data) => {setTimeout(() => {console.log(data) },6000); return data + 1;}) // 2
.then((data) => {setTimeout(() => {console.log(data) },9000); return data + 1;}) // 3
async / await
Promise
можно вернуть из любой функции. Javascript
автоматически обернет ответ из функции в Promise
.
Добавляем ключевое слово async.
async
- говорит функции, что она будет возвращать Promise
.
1
2
3
4
5
6
7
8
9
10
11
12
async function test(){
return 'value';
}
test() // Promise { <state>: "fulfilled", <value>: "value" }
// или так
const test2 = async () => {
return 'value';
}
test2() // Promise { <state>: "fulfilled", <value>: "value" }
Данные из функции при наличии async будут обернуты в
Promise
в любом случае.
Далее естественно нужно обработать результат с помощью then
и catch
.
1
2
3
4
5
6
7
8
9
async function test() {
return new Promise(resolve => {
setTimeout(() => {
resolve(1);
},2000)
});
}
test().then((data) => console.log(data)); // через 2 сек результат 1
Как бы сделать, чтобы не писать then
каждый раз для обработки Promise
, для этого есть ключевое слово await
.
await
работает только в модулях и в асинхронных функциях.
То есть await
ждет выполнения асинхронной функции.
Перепишем пример.
1
2
3
4
5
6
7
8
9
10
11
12
async function test() {
let p = new Promise(resolve => {
setTimeout(() => {
resolve(1);
},2000)
});
let result = await p;
console.log(result);
}
test() // вот так получили результат 1
async/await
Рассмотрим в отдельной заметке.
Что в итоге
В итоге у нас получится пример такая схема работы Promise
:
1
2
3
4
5
6
7
8
9
Promise - состояние pending
fulfilled - успешно разрешен
.then - вызов метода then
return new Promise - возврат Promise со статусом pending
.then - снова обрабатываем и так много раз пока не придем к результату
rejected - отклонен
.catch
error - ошибка
.then - если есть второй параметр
Promise
является основой для понимания современного асинхронного программирование на javascript
.
Promise
нужен для выполнения асинхронной операции. С его использованием код становится чище.
Promise
возвращает метод fetch
, который мы разберем в одной из будущих заметок.