Запрос к api с тайм-аутом

slkarol

Stanislav Karol

Posted on March 5, 2021

Запрос к api с тайм-аутом

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

Есть функция api, которая обращается к адресу и возвращает строку,- токен. Её сигнатуру (контракт) можно описать так:

type Api=(url: string) => Promise<string>;
Enter fullscreen mode Exit fullscreen mode

В задаче нужно обратиться к двум адресам и сравнить строки, если совпадают, то вывести в консоль строку.
Если не совпадают - вывести в консоль строку 'Unauthorized'.
Если один из сервисов не ответил - вывести в консоль строку 'Forbidden'
Если один из сервисов отвечает больше 10 секунд - вывести в консоль строку 'Timeout'.

Для решения этой задачи нам понадобятся два метода работы с промисами: race и all .
И вот для чего нам это нужно.
Определим функцию, которая будет ожидать заданное количество миллисекунд. Эта функция будет выполнять роль таймера- если прошло 10 секунд, то выдать ошибку.

function wait(ms = 10000) {
  // Вернуть промис, возвращающий через 10 секунд ошибку:
  // Запуск функции reject c параметром "Timeout"
  return new Promise((resolve, reject) => {
    setTimeout(reject, ms, "Timeout");
  });
}
Enter fullscreen mode Exit fullscreen mode

Далее определим промис для запроса токена:

const request1 = api("oauth-url");
Enter fullscreen mode Exit fullscreen mode

А теперь напишем промис, который будет опрашивать токен с тайм-аутом ожидания:

const promise1 = Promise.race([wait(), request1]);
Enter fullscreen mode Exit fullscreen mode

Вот на этой строке и держится всё решение. Первый элемент массива- это функция-таймер, которая выбрасывает исключение, второй элемент- это запрос токена. Если запрос будет успешным, то промис вернёт его результат; если продлится больше 10 секунд, то сработает исключение с параметром "Timeout".
Эта строка и есть краеугольный камень решения, который имеет практическую ценность. В чём она: Можно ограничить тайм-аут у fetch'a (возможно в практике у Вас такое было: запрос к апи мог длиться больше двух минут и пора бы уже прекращать ожидание ответа).

Аналогично подготовим второй промис:

const request2 = api("ldap-url");
const promise2 = Promise.race([wait(), request2]);
Enter fullscreen mode Exit fullscreen mode

Для выполнения этой части задания "Если один из сервисов не ответил..." нам пригодится Promise.all, который выбрасывает ошибку, если один из сервисов не ответит:

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

Что выше написано: Если строки равны, то вывести первую строку, иначе вывести Unauthorized. Если же какой-то из запросов api будет с ошибкой, то в консоль будет выведена ошибка.

Задача почти готова. Но что-то не хватает. Когда мы посмотрим на текст программы, мы не увидим где задаётся ответ в виде строки "Forbidden". У нас есть вывод в консоль ошибки:

  (err) => {
    console.error(err);
  }
Enter fullscreen mode Exit fullscreen mode

И этот вывод может показать строку "Timeout", но если запрос к api будет ошибочным, то мы получим объект Error. Поэтому давайте промис запроса перепишем:

const promise1 = Promise.race([wait(), request1])
  .then((res1) => res1)
  .catch((e) => {
    throw e === "Timeout" ? "Timeout" : "Forbidden";
  });
Enter fullscreen mode Exit fullscreen mode

Итак, мы подошли к решению. Вот его полный текст:

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);
  }
);
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
slkarol
Stanislav Karol

Posted on March 5, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related