Link Search Menu Expand Document
20 Апреля 2024 г.

Используем Promise

Содержание

Уже делал обзор на promise в статье https://lexusalex.ru/javascript-7-promise-basics

Пришло время более подробно поговорить об этом.

Проблема

Часто в коде можно встретить зависимые асинхронные запросы друг от друга.

Например, имеем функцию request, которая делает ajax запрос на сервер

function request(url, success, error) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url); // инициализация запроса (async)
    xhr.send(); // отправка запроса
    xhr.onload = () => {
        if (xhr.status !== 200) {
            return xhr.statusText;
        } else {
            const result = JSON.parse(xhr.response).data[0];
            success(result);
        }
    };

    xhr.onerror = () => {
        const e = new Error('Failed request');
        error(e);
    };
}

И делаем 5 зависимых (!!!) друг от друга запросов с обработкой ошибок.

Функция getInfo()

function getInfo() {
    request(
        'https://fakerapi.it/api/v1/addresses?_quantity=1',
        (result) => {
            console.log(result);
            request(
                'https://fakerapi.it/api/v1/custom?_quantity=1&city=city&streetName=streetName',
                (result2) => {
                    console.log(result2);
                    request(
                        'https://fakerapi.it/api/v1/custom?_quantity=1&phone=phone&',
                        (result3) => {
                            console.log(result3);
                            request("https://fakerapi.it/api/v1/addresses?_quantity=1", (result4) => {
                                console.log(result4);
                                request("https://fakerapi.it/api/v1/images?_quantity=1&_type=kittens&_height=300", (result5) => {
                                    console.log(result5);
                                }, (err5) => {
                                    console.log(err5);
                                })
                            }, (err4) => {
                                console.log(err4);
                            })
                        },
                        (err3) => {
                            console.error(err3);
                        }
                    );
                },
                (err2) => {
                    console.error(err2);
                }
            );
        },
        (err) => {
            console.error(err);
        }
    );
}

getInfo();

Посмотрим код:

f1(() => {
    f2()
    f3(() => {
        f4(() => {
            f5(() => {
                f6(() => {
                    f7(() => {})
                })
            })
        })
    })
})

Что тут происходит, что за чем вызывается, сложно…

Получается ад колбэков.

Минусы

Какие минусы этого подхода:

  • Код тяжело читать, так как он сдвигается вправо, особенно когда много вычислений между запросами, сложная логика обработки
  • Жесткая сцепленности кода
  • Если упал 3 запрос, то 4 и 5 никогда не выполнятся

Пример

Результаты запрсов можно увидеть в консоли браузера.

Открыть пример 18.1 в новой вкладке

Что такое promise

Это более удобный механизм для работы с асинхронным кодом, js объект со скрытыми полями.

Создание

// Создадим `Promise`
const p = new Promise(); // Будет ошибка
// Внутрь промиса нужно передать обязательно функцию обратного вызова
const p = new Promise(() => {console.log('exec')}); // Вот сейчас сработает
// Или так
function exec(){
    console.log('exec');
}
const p = new Promise(exec); // Но чаще первый вариант

Resolve

Функция которую нам предоставляет promise, чтобы разрешить свое состояние. По умолчанию - это pending, значит незавершенный запрос.

Мы сами решаем что успешно, а что нет.

const p = new Promise(resolve => {
    resolve(1); // Главное вызвать функцию
});

console.log(p); // Теперь state будет fulfilled, а значение будет 1
// На самом деле можно ничего не передавать, тогда в value будет undefined, но разрешить пропим успешно нужно.
const p = new Promise((resolve) => {
    resolve();
});
  • Функция resolve() должна быть вызвана в любом случае, тогда промис завершится успешно.

Reject

Второй параметр reject, если запрос не выполнился, или произошла ошибка.

const p = new Promise((resolve, reject) => {
  reject(new Error(123)); // Состояние rejected
});

console.log(p);

Состояния

  • pending - ожидает выполнение
  • fulfilled - успешно завершился resolve()
  • rejected - произошла ошибка, отклонен. reject()

  • Если не вызвать resolve() или reject() будет всегда состояние pending

Состояние промиса можно менять только один раз, например

 const p = new Promise((resolve, reject) => {
      resolve(1); // Будет выполнена только эта функция, все что после не будет выполнено
      resolve(777);
      reject(2);
      resolve(345);
      reject(555);
  });
  console.log(p);

Callback выполняется синхронно в момент вызова.

Обработка результата

  • then
  • catch
  • finally

then

Код после успешного выполнения promise

Then будет вызван только тогда, когда будет вызван resolve()

 const p = new Promise((resolve, reject) => {
        // Любой код
        resolve(777);
  });

  p.then((resolveResult) => { // Обязательный параметр
      console.log(resolveResult); // Тут будет 777
  })

При возникновении ошибки передаем вторым параметром rejectResult и обрабатываем ошибку

const p = new Promise((resolve, reject) => {
  reject('error');
});

p.then((resolveResult) => {
  console.log(resolveResult);
}, ((rejectResult) => { // Этот колбэк необязательный
  console.log(rejectResult);
}))

Внимание callback rejectResult обрабатывает не все ошибки

Promise можно создать с использованием статической функции.

const p1 = Promise.resolve(3456); // Автоматически разрешить промис
const p2 = Promise.reject('error2'); // Завершить промис
// Обработка такого варианта идет так же через then

catch

Ловит все ошибки, которые возникают в then

const p = new Promise((resolve, reject) => {
    resolve(123); // Разрешаем промис
});

p.then((resolveResult) => {
    console.log(resolveResult); // 123
    throw new Error('error');
}, ((rejectResult) => {
    console.log(rejectResult);
})).catch((error) => {
    console.log(error.message); // ловим error
})

Каждый обработанный Promise неявно возвращает новый промис

p1.then(() => {
}).then(() => {
}).catch(() => {
}).then(() => {
}).then(() => {
}).then(() => {
})

Можно навесить одну секцию catch на всю цепочку промисов, и если в одном из промисов что-то пойдет не так, мы обязательно попадем в эту секцию

Можно делать такие штуки

const p1 = new Promise((resolve,reject) => {
      resolve(4);
  })

  const n = p1.then((resolveResult) => {
      return resolveResult;
  });

  n.then((data) => {
      console.log(data + 4); // Тут уже будет 8
  })

Или вот такие удивительные вещи:

const p1 = new Promise((resolve, reject) => {
        resolve(4);
    })

    p1.then((resolveResult) => {
        console.log(resolveResult);
        return resolveResult;
    }).then((result2) => {
        result2 = result2 + 2;
        console.log(result2);
        return result2;
    }).then((result3) => {
        result3 = result3 + 2;
        console.log(result3);
        return result3;
    }).then((result4) => {
        result4 = result4 + 2;
        console.log(result4);
        return result4;
    });

Код получается линейным и легко читается, это называется цепочка промисов.

finally

Если нужно что-то сделать после завершения промиса и не важно успешно или нет, то используем finally.

Пример:

const promise = new Promise((resolve) => {
        resolve(10);
    })

    promise.then((res) => {
        return res;
    }).then((res) => {
        res = res + 10;
        if (res === 20) {
            throw Error('some error');
        }
        console.log(res);
        return res;
    }).then((res) => {
        console.log(res);
        return res + 10;
    }).catch((error) => {
        console.log(error); // Обрабытывем ошибки
    }).finally(() => {
        console.log('finally'); // Выполнится в любом случае
    })

Примеры

Открыть пример 18.2 в новой вкладке

Перепишем первый пример на Промисы и в консоли увидим результат.

Открыть пример 18.3 в новой вкладке

Примеры

Выполнение двух setTimeout c задержкой три секунды

Промис должен возвращать промис

const p = new Promise((resolve, reject) => {
      setTimeout(() => {
          // fetch любой ассинхронный код
          resolve({id: 1, name: 'Test'});
      }, 3000)
  })

  p.then((res) => {
      console.log(res);
      return res;
  }).then((res) => {
      setTimeout(() => {
          // fetch
          console.log({id: 2, name: 'Test2'});
      }, 3000)
      return res;
  })

TODO

Promise.all - ждем все промисы, если хоть один с ошибок - catch, иначе then

Promise.allSettled - просто ждем выполнения всех промисов (всегда then)

Promise.race - получаем первый выполнившийся промис (если он был с ошибкой - catch, иначе then)

Promise.any - получаем первый УСПЕШНО выполнившийся промис


Возник вопрос или предложение пиши на почту alexsey_89@bk.ru или в Телеграмм канал

Дата публикации: 20 Апреля 2024 г.

Содержание


Наверх

Последнее изменение страницы: 20 Апреля 2024 г.