Promise javascript
Самый простой способ работы с асинхронным кодом это callback.
Promise
Promise – это специальный объект, который содержит своё состояние.
Абстракция для обещания асинхронных данных
Обещание можно выполнить, но можно и не сдержать
Создание и использование
Fulfilled (выполнено)
Pending (состояние ожидания данных)
Rejected (отклонено)
Такой promises выглядит как обертка над некоторой асинхронной логикой (пример):
Javascript
// Такой promises выгдялет как обертка над некоторой асинхронной логикой
var promise = new Promise(function(resolve,reject){
var xhr = new XMLHttRequest();
xhr.open(method, url);
xhr.onload = function(){ // в момент, когда получаем данные подаем данные на код resolve
resolve(xhr.response);
}
xhr.onerror = function(){ // если error подаем данные в rejecte
reject(new Error('network request failed'));
}
xhr.send();
});
promise.then(function(response){
// обработка результата
});
promise.catch(function(error){
//обработка ошибки
})
XMLHttpRequest, GET и promises
Javascript
function get(url) {
// Return a new promise.
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
// Resolve the promise with the response text
resolve(req.response);
}
else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.send();
});
}
Использование:
Javascript
get('story.json').then(function(response) {
console.log("Success!", response);
}, function(error) {
console.error("Failed!", error);
});
html5rocks.com/
Цепочки:
Зачем использовать promise? Promise позволяют делать цепочки обработки из нескольких синхронных операций. См. след. Пример.
then
это не конец истории, вы можете сцепить then
вместе, чтобы трансформировать знаение или запустить
дополнительное асинхронное действие после предыдущего.
1. Трансформируем значение
Javascript
var promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise.then(function(val) {
console.log(val); // 1
return val + 2;
}).then(function(val) {
console.log(val); // 3
});
практически пример:
Javascript
get('story.json').then(function(response) {
return JSON.parse(response);
}).then(function(response) {
console.log("Yey JSON!", response);
});
2. Очередь из асинхронных действий
Вы можете использовать then
для запуска асинхронных действий в последовательности.
Когда вы вернули что-то из callback then
происходиит немного магии.
Если вы возвращаете значение, следующий then
запустится с этим значением. Однако если вы вернете что-то
promise-подобное, следующий then будет вызван как только этот promise разрешится. Например:
Javascript
getJSON('story.json').then(function(story) { //дает нам набор URL-адресов
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
console.log("Got chapter 1!", chapter1);
});
3. Цепочка с catch
Методы .then()
и .catch()
возвращают Promise. Можно строить цепочки:
Javascript
//новый встроенный метод для ajax-запросов (возвращает promise)
fetch('https://api.github.com/users/user')
.then(function(response){
if(response.status === 200){
return response;
}
throw new Error(response.status);
})
.then(function(response){
return response.json();
})
.then(function(data){
console.log(data.name);
})
.catch(function(error){
console.log(error.stack);
})
4. Promise в классе (es2015)
// PROMISE EXAMPLE:
send(data){
let self = this,
url = self.urlAdd,
wait = new Promise(function(resolve, reject){
$.ajax({
url:url,
type:'POST',
data:data,
success: function(data){
resolve(data);
},
error: function(data){
reject(data);
}
})
});
wait.then(
result => {
self.addSuccess(result);
},
error => {
self.addError(error);
}
)
}
addSuccess(data){
if (data.success=='1') {
//...
}
else
{
//...
}
}
addError(data){
//...
}
// end PROMISE
Promise и setTimeout
'use strict';
// Создаётся объект promise
let promise = new Promise(function (resolve, reject) {
setTimeout(function() {
// переведёт промис в состояние fulfilled с результатом "result"
resolve("result");
}, 1000);
});
// promise.then навешивает обработчики на успешный результат или ошибку
promise
.then(
function result(result) {
// первая функция-обработчик - запустится при вызове resolve
alert("Fulfilled: " + result); // result - аргумент resolve
},
function result(error) {
// вторая функция - запустится при вызове reject
alert("Rejected: " + error); // error - аргумент reject
}
);
// через 1 секунду выведется «Fulfilled: result»
Обработка исключений
Promise не потеряет ни одного исключения!
Каждый раз, когда в promise встречается исключение создается promise в состояние отклонен.
Это позволяет структурировать код:
Javascript
// обработка исключений
// в конструкторе вызван reject()
var promise = new Promise(function(resolve, reject){
//...
reject(new Error('goodbye world')); // возможно в асинхронной лперации
//...
});
// сгенерировано исключение в конструкторе,либо в обработчике
promise = promise.then(function(result){
//...
throw new Error('goodbye world');
//...
});
В цепочке исключение обрабатывается в ближайшем .catch()
Javascript
// В цепочке исключение обрабатывается в ближайшем .catch()
.then(function(result){
//...
throw new Error('goodbye world');
})
.then(function(result){
return process1(data); // обработчик не будет выполнен
})
.catch(function(error){
console.log(error.stack);
return FALLBACK_DATA; // восстановление после ошибки
})
.then(function(data){
return process2(data); // продолжаем
})
Это аналог try – catch в асинхронном виде.
Дополнительно
Promise.all([promise1, promise2, …])
Дожидаемся, когда все промисы будут выполнены
Promise.race([promise1, promise2, …])
Завершится как только любой (один) промис завершится
Есть возможность создавать промисы сразу resolve или reject:
Promise.resolve(value)
Promise.reject(error)
Пример использование Promise
Исходники по мотивам замечательных видео от CodeDojo.
// PROMISE:
function applyForVisa(documents) {
console.log('обработка заявления....');
let promise = new Promise(function(resolve, reject){
setTimeout(function(){
Math.random() > 0
? resolve({ status: "success"})
: reject("В визе отказано: не те документы");
}, 1000); // симулируем задержку документов
});
return promise;
}
function getVisa(visa){
// then автоматически создает новое обещание и передает его дальше по цепочке
/********** I ВАРИАНТ *********/
/*
console.dir(visa);
console.info("Виза получена ХОП");
// используем return, чтобы передать visa как параметр next Promise (в функцию bookHotel)
return visa;
*/
/********** II ВАРИАНТ (вернем обещание непосредственно) *********/
console.log("visa from getVisa", visa);
console.info("Виза получена ХОП");
return new Promise(function (resolve, reject) {
setTimeout(() => {
resolve(visa)
}, 2000)
});
}
function bookHotel(visa) {
// then автоматически создает новое обещание и передает его дальше по цепочке
/********** I ВАРИАНТ *********/
/*
console.log("visa: ", visa);
console.log("Бронируем отель");
return {status: "success Hotel"};
*/
console.log("visa from bookHotel: ", visa);
console.log("Бронируем отель");
return new Promise(function (resolve, reject) {
//reject("НЕТ МЕСТ")
resolve({status: "success Hotel"});
});
}
function buyTickets(booking) {
console.log("Покупаем билеты");
console.log("Бронь: ", booking);
}
applyForVisa({ data: "docs"})
// then автоматически создает новое обещание и передает его дальше по цепочке
.then(
getVisa
).
then(
bookHotel
).
then(
buyTickets
).
catch(
(error) => {
console.error(error)
}
);
/*************************** CALLBACK HELL: ************************************/
/*
// чтобы вернуть visa (из setTimeout) определим 2-м параметр ф-ю обратного вызова:
// если нам не одобряют документы воспользуемся еще одним callback - 3-й параметр
function applyForVisa(documents, resolve, reject) {
console.log('обработка заявления....');
setTimeout(function(){
Math.random() > .5 ? resolve({ kuku: "kuku"}) : reject("В визе отказано: не те документы");
let visa = {};
}, 2000); // симулируем задержку документов
}
applyForVisa(
{ data: "docs"},
function(visa){
console.info("Виза получена");
// после того как мы получили визу:
bookHotel(visa, function(reservation){
buyTickets(reservation, function () {
}, function () {
})
}, function(error){
} );
},
function(reason){
console.error(reason);
}
);
*/
// CALLBACK HELL нам помогут решить обещания
Применение Promise на практике
/*********Использование обещаний на практическом примере************/
/*
IE не поддерживает обещания.
Решить эту проблему нам поможет http://babeljs.io/docs/usage/polyfill/
*/
// Перепишем следующий пример на PROMISE
/*
let movieList = document.getElementById('movies');
function addMovieToList(movie) {
let img = document.createElement("img");
img.src = movie.Poster;
movieList.appendChild(img);
}
function getData(url, done) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function () {
if(xhr.status === 200) {
let json = JSON.parse(xhr.response);
console.log(json);
done(json.Search);
}
else {
console.error(xhr.statusText);
}
};
xhr.onerror = function (error) {
console.error(error);
};
xhr.send();
}
let search = "batman";
getData(`http://www.omdbapi.com/?s=${search}`, function (movies) {
movies.forEach(function (movie) {
addMovieToList(movie);
})
})
*/
let movieList = document.getElementById('movies');
function addMovieToList(movie) {
let img = document.createElement("img");
img.src = movie.Poster;
movieList.appendChild(img);
}
function getData(url) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function () {
if(xhr.status === 200) {
let json = JSON.parse(xhr.response);
console.log(json);
resolve(json.Search);
}
else {
reject(xhr.statusText);
}
};
xhr.onerror = function (error) {
reject(error);
};
xhr.send();
})
}
let search = "batman";
/*
getData(`https://www.omdbapi.com/?s=${search}`)
.then(movies =>
movies.forEach(movie =>
addMovieToList(movie)))
.catch((error) => {console.error(error)});
*/
/*******LET & RACE на конкретном примере**********/
let lady = getData(`https://www.omdbapi.com/?s=lady`);
let man = getData(`https://www.omdbapi.com/?s=man`);
/*
batman
.then(movies =>
movies.forEach(movie =>
addMovieToList(movie)))
.catch((error) => {console.error(error)});
superman
.then(movies =>
movies.forEach(movie =>
addMovieToList(movie)))
.catch((error) => {console.error(error)});
*/
Promise.race([lady, man])
.then(movies =>
movies.forEach(movie =>
addMovieToList(movie)))
.catch((error) => {console.error(error)});;
/*******LET & RACE**********/
function go(num) {
return new Promise(function (resolve, reject) {
let delay = Math.ceil(Math.random() * 3000);
//console.log("num: ", num);
//console.log("delay: ", delay);
setTimeout(() => {
if (delay > 2000) {
reject(num);
}
else {
resolve(num+"ku");
}
}, delay)
})
}
let promise1 = go(1);
let promise2 = go(2);
let promise3 = go(3);
/************ALL************/
// допустим нам необходимо дождаться выполнений всех обещаний,
// для этого воспользуемся методом all
// в качестве аргумента принимает массив обещаний
//https://developer.mozilla.org/ru/docsReference/Global_Objects/Promise/resolve
Promise.all([promise1, promise2, promise3])
.then(value => console.log("value: ", value))// в value находится массив из значений
.catch(error => console.error(error)); // выводит значение, в котором произошла ошибка
// которые возвращают обещания
/************RACE************/
// допустим нам не важно, чтобы выполнились все обещания
// нам важно получить результат от самого первого, которое выполнится
// для этого есть метод race
Promise.race([promise1, promise2, promise3])
.then(value => console.log("value: ", value))
.catch(error => console.error(error));
fetch
fetch()
позволяет вам делать запросы, схожие с XMLHttpRequest
(XHR). Основное отличие заключается в том, что Fetch
API использует Promises
.
let promise = fetch(url[, options]);
/*
url – URL, на который сделать запрос,
options – необязательный объект с настройками запроса.
method – метод запроса,
headers – заголовки запроса (объект),
body – тело запроса: FormData, Blob, строка и т.п.
mode – одно из: «same-origin», «no-cors», «cors», указывает,
в каком режиме кросс-доменности предполагается делать запрос.
credentials – одно из: «omit», «same-origin», «include», указывает,
пересылать ли куки и заголовки авторизации вместе с запросом.
cache – одно из «default», «no-store», «reload», «no-cache», «force-cache»,
«only-if-cached», указывает, как кешировать запрос.
redirect – можно поставить «follow» для обычного поведения при коде 30x
(следовать редиректу) или «error» для интерпретации редиректа как ошибки.
*/
fetch( this.get_path(path),
this.merge_options(Object.assign({method: method}, options))).then(response => {
RequestEvents.dispatch(method, [response]);
resolve(this.prepare_response(response))
}).catch(err => {
reject(err);
});
fetch
fetch()
позволяет вам делать запросы, схожие с XMLHttpRequest
(XHR). Основное отличие заключается в том, что Fetch
API использует Promises
.
let promise = fetch(url[, options]);
/* Свойства options:
url – URL, на который сделать запрос,
options – необязательный объект с настройками запроса.
method – метод запроса,
headers – заголовки запроса (объект),
body – тело запроса: FormData, Blob, строка и т.п.
mode – одно из: «same-origin», «no-cors», «cors», указывает,
в каком режиме кросс-доменности предполагается делать запрос.
credentials – одно из: «omit», «same-origin», «include», указывает,
пересылать ли куки и заголовки авторизации вместе с запросом.
cache – одно из «default», «no-store», «reload», «no-cache», «force-cache»,
«only-if-cached», указывает, как кешировать запрос.
redirect – можно поставить «follow» для обычного поведения при коде 30x
(следовать редиректу) или «error» для интерпретации редиректа как ошибки.
*/
fetch( this.get_path(path),
this.merge_options(Object.assign({method: method}, options))).then(response => {
RequestEvents.dispatch(method, [response]);
resolve(this.prepare_response(response))
}).catch(err => {
reject(err);
});
Комментарии к статье