Открыть меню    

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);
});

Комментарии к статье