Запрос к api с тайм-аутом
Published: 2021-03-05 11:51:27

Задача, которую я встретил, может иметь цель не только проверки теоретической подготовки, но есть в ней и практическая ценность.

Есть функция **api**, которая обращается к адресу и возвращает строку,- токен. Её сигнатуру (контракт) можно описать так:
```typescript
type Api=(url: string) => Promise<string>;
```
В задаче нужно обратиться к двум адресам и сравнить строки, если совпадают, то вывести в консоль строку.
Если не совпадают - вывести в консоль строку *'Unauthorized'*.
Если один из сервисов не ответил - вывести в консоль строку *'Forbidden'*
Если один из сервисов отвечает больше 10 секунд - вывести в консоль строку *'Timeout'*.

Для решения этой задачи нам понадобятся два метода работы с промисами: [race](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) и [all](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) .
И вот для чего нам это нужно.
Определим функцию, которая будет ожидать заданное количество миллисекунд. Эта функция будет выполнять роль таймера- если прошло 10 секунд, то выдать ошибку.
```js
function wait(ms = 10000) {
  // Вернуть промис, возвращающий через 10 секунд ошибку:
  // Запуск функции reject c параметром "Timeout"
  return new Promise((resolve, reject) => {
    setTimeout(reject, ms, "Timeout");
  });
}
```
Далее определим промис для запроса токена:
```js
const request1 = api("oauth-url");
```
А теперь напишем промис, который будет опрашивать токен с тайм-аутом ожидания:
```js
const promise1 = Promise.race([wait(), request1]);
```
Вот на этой строке и держится всё решение. Первый элемент массива- это функция-таймер, которая выбрасывает исключение, второй элемент- это запрос токена. Если запрос будет успешным, то промис вернёт его результат; если продлится больше 10 секунд, то сработает исключение с параметром "Timeout".
Эта строка и есть краеугольный камень решения, который имеет практическую ценность. В чём она: Можно ограничить тайм-аут у fetch'a *(возможно в практике у Вас такое было: запрос к апи мог длиться больше двух минут и пора бы уже прекращать ожидание ответа)*.


Аналогично подготовим второй промис:
```js
const request2 = api("ldap-url");
const promise2 = Promise.race([wait(), request2]);
```
Для выполнения этой части задания *"Если один из сервисов не ответил..."* нам пригодится Promise.all, который выбрасывает ошибку, если один из сервисов не ответит:
```js
Promise.all([promise1, promise2]).then(
  ([s1, s2]) => {
    const re = s1 === s2 ? s1 : "Unauthorized";
    console.log(re);
  },
  (err) => {
    console.error(err);
  }
);
```
Что выше написано: Если строки равны, то вывести первую строку, иначе вывести *Unauthorized*. Если же какой-то из запросов api будет с ошибкой, то в консоль будет выведена ошибка.

Задача почти готова. Но что-то не хватает. Когда мы посмотрим на текст программы, мы не увидим где задаётся ответ в виде строки "Forbidden". У нас есть вывод в консоль ошибки:
```js
  (err) => {
    console.error(err);
  }
```
И этот вывод может показать строку *"Timeout"*, но если запрос к api будет ошибочным, то мы получим объект Error. Поэтому давайте промис запроса перепишем:
```js
const promise1 = Promise.race([wait(), request1])
  .then((res1) => res1)
  .catch((e) => {
    throw e === "Timeout" ? "Timeout" : "Forbidden";
  });
```
Итак, мы подошли к решению. Вот его полный текст:
```javascript
function wait(ms = 10000) {
  // Вернуть промис, возвращающий через 10 секунд ошибку:
  // Запуск функции reject c параметром "Timeout"
  return new Promise((resolve, reject) => {
    setTimeout(reject, ms, "Timeout");
  });
}

const request1 = api("oauth-url");
const promise1 = Promise.race([wait(), request1])
  .then((res1) => res1)
  .catch((e) => {
    throw e === "Timeout" ? "Timeout" : "Forbidden";
  });

const request2 = api("ldap-url");
const promise2 = Promise.race([wait(), request2])
  .then((res2) => res2)
  .catch((e) => {
    throw e === "Timeout" ? "Timeout" : "Forbidden";
  });

Promise.all([promise1, promise2]).then(
  ([s1, s2]) => {
    const re = s1 === s2 ? s1 : "Unauthorized";
    console.log(re);
  },
  (err) => {
    console.error(err);
  }
);
```