Под катом, небольшая заметка о том, как приостановить выполнение вашего приложения при обрыве связи с интернетом и продолжить — когда она будет восстановлена.

Представим, что ваше, гипотетическое приложение, должно выполнить очередь http запросов. При чем каждый следующий зависит от результата предыдущего.

async function main () {
  let url = ' ... '

  while (url) {
    const resp = await fetch(url)
    const json = await resp.json()
    url = json.url
  }
}

Но если посреди этого процесса пропадёт связь с интернетом, то очередь прервётся. Давайте исправим это.

Для начала напишем функцию, которая будет возвращать промис, который будет решатся, в момент, когда клиент возобновит соединение:

function awaitOnline () {
  return new Promise(resolve => {
    // Если клиент уже online — немедленно возвращаем результат
    if (navigator.onLine) {
      resolve()
      return
    }

    // Регистрируем обработчик и решаем промис как только клиет будет online
    window.addEventListener(
      'online',
      () => resolve(),
      {once: true} // Автоматически удаляем обработчик после первого события
    )
  })
}

Теперь добавим её в наш основной код

async function main () {
  let url = ' ... '

  while (url) {
    await awaitOnline() // Выполнение приостановится в этом месте до момента возобновления сети
    const resp = await fetch(url)
    const json = await resp.json()
    url = json.url
  }
}

Таким простым образом, при каждой итерации наш алгоритм будет проверять статус сети, и встанет на паузу если соединения не будет. И автоматически продолжит выполнение с того же места, как только связь будет восстановлена.

Конечно, это не панацея. Но такой, небольшой хак, сделает ваше приложение чуть более отказоустойчивым.

Комментарии (13)


  1. propell-ant
    17.01.2019 11:50

    Может всё-таки не каждый раз подписываться на событие window.online, а один раз подписаться и возвращать промис?
    А концепция — да понятно.


  1. IRT
    17.01.2019 12:28

    В Chrome navigator.onLine показывает не фактическое нахождение в интернете, а лишь наличие соединения с LAN / роутером. Так что толку от navigator.onLine мало, попробуйте на этой странице нажать F12 и на вкладке Network поставить галку Offline, ничего не изменится:
    bug336359.bmoattachments.org/attachment.cgi?id=220609


  1. vintage
    17.01.2019 13:44

    А повторять отвалившийся из-за оффлайна запрос кто будет?


    1. cerbernar
      17.01.2019 14:12

      Так ведь смысл в том, что запрос не отправится, пока не появится сеть.


      1. Kozack Автор
        17.01.2019 14:14

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


        1. taliban
          17.01.2019 15:17

          урл не перепишется и в следующий раз пойдет тот же запрос, но ексепшен словить да, было бы неплохо


    1. Perlovich
      17.01.2019 14:30

      имхо в большинстве случаев это лишнее и лучше просто показать пользователю сообщение, что имеются проблемы с сетью и попросить его перезагрузить страницу.

      Так, во-первых, пользователь будет информирован, почему его приложение не отвечает на его действия (и перестанет тыкать элементы управления, вызывая ещё больше запросов). Во-вторых, так гораздо проще разрабатывать/поддерживать. Если мы хотим дать гарантию, что наше приложение нормально работает, если потерялось соединение, то это гораздо большее время тестирования и гораздо больше сценариев, которые надо проверить. Ну, и в третьих, если наш клиент автоматически пробует повторить запрос, то нужно дополнительно думать об идемпотентности PUT/POST/PATCH запросов.

      Исключением являются ситуации, когда пользователь мог потратить большое количество времени, делая что-то (например, набирая текст статьи). Мы не хотим приводить пользователя в фрустрация, не сохранив всю его работу или хотя бы не предоставив ему возможность сохранить её вручную.


      1. vintage
        17.01.2019 14:49

        Об идемпотентности надо думать даже, если пользователь вручную переотправляет запрос. Поэтому я POST запросы никогда не использую. Вообще, правильный апи, на мой взгляд реализует только два метода: GET и PUT/PATCH.


      1. Zenitchik
        17.01.2019 16:17

        имхо в большинстве случаев это лишнее и лучше просто показать пользователю сообщение, что имеются проблемы с сетью и попросить его перезагрузить страницу.

        Ага. Пользователь проходил тест, на клиенте куча данных, открытая сессия SCORM, которую придётся переоткрывать. «Перезагрузить страницу»…

        В тех случаях, когда нет необходимости повторять запрос — сообщать о проблемах с сетью необходимости тоже нет. Пользователь, чай, не дурак, 404 разглядит.


  1. WanSpi
    17.01.2019 14:14

    Заголовок слишком громкий, я думал тут покажут как остановить все скрипты одновременно…


  1. coramba
    17.01.2019 14:15

    Не совсем понятны два момента
    1. зачем нужен while ($url)?
    2. почему не сделать полностью на промисе?

    awaitOnline() 
        .then( // we're online
            ()=>fetch(url)
                    .then(/*...*/)
                    .catch(msg=>console.error(msg));
        )
        // something went wrong
        .catch(msg=>console.error(msg));
    



    1. Kozack Автор
      17.01.2019 14:20

      1. Это бесконечный цикл, который будет выполнять один запрос за другим, до тех пор, пока в ответе приходит новый УРЛ. Это весьма утрированный пример, но он, как мне кажется, отлично демонстрирует общий алгоритм. В более реальном сценарии, это был бы какой-нибуть импорт большого числа коментариев на сервер (вам нужно получить ID созданного коментария, прежде чем импортировать дочерние), или скажем, обход всех страниц пагинации (вам нужно получить ссылку на следующую страницу, прежде чем загрузить записи).

      2. Не совсем понял ваш вопрос. Мой пример тоже полностью основан на промисах.


      1. coramba
        17.01.2019 14:57

        Пардон, невнимательно прочел постановку задачи. Вопросы снимаю :)