Некоторое время назад я загнал себя в угол. Я переработал кучу кода до множества детализированных функций. Я был доволен результатом, но понял, что начал вызывать некоторые вещи асинхронно, которые записывались поверх ответов друг друга по мере их разрешения. Это вызвало небольшой хаос. Я сделал нерешительную попытку рефакторинга этой части кода без сохранения состояния, но в конце концов решил принять технический долг и реализовать более легкое краткосрочное решение. То есть, я бы запускал эти различные асинхронные вызовы последовательно, чтобы не происходило «топтание друг на друга».

Я подозреваю, что многие люди поступают так же, как и я, когда сталкивался с этой проблемой. Я изобрел небольшую систему отслеживания обещаний. Я заранее знал, сколько обещаний мне нужно выполнить, поэтому я вызвал одно и ждал, пока оно разрешится. Если бы мне еще оставалось выполнить что-то еще, я бы включил счетчик и вызвал следующий, и так далее, пока все операции не будут завершены. Реализовать это было достаточно просто, но казалось, что это неуместно. Я провел небольшое исследование и нашел кое-что очень интересное на машине переполнения стека. Мои глаза почти потускнели, когда я впервые увидел это, но он меня ел, поэтому я пошел и реализовал последовательный асинхронный вызов в традициях школы reduce ().

Настройка решения

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

PromiseFn - это функция JS, которая принимает два параметра: URL-адрес веб-сайта и идентификатор отслеживания. При вызове любой объект promisesFn запускает GetSomething () на _sequentialPromisesService, передавая URL-адрес и идентификатор отслеживания. URL-адрес, очевидно, ведет к веб-сайту, а идентификатор отслеживания предназначен для демонстрации последовательности процесса путем записи его прогресса в консоль.

[В стороне - «последовательность» - я люблю английский язык :)]

_sequentialPromiseService - это сервис Angular, но в целом это не имеет значения. Этот код выглядит так:

Здесь есть немного угловатой треп. Ключевым моментом является то, что вызов $ http.get () в конечном итоге извлекает указанный URL. Он асинхронный, и как только он получает веб-страницу, он регистрирует этот факт и разрешает обещание с одноразовым значением «сработало». В реальном мире он будет возвращать фактические данные.

Последовательный запуск вещей

Давайте посмотрим, как выполнить несколько таких вызовов последовательно. Обратите внимание на следующий код:

Выполнить последовательно определяет небольшую лямбда-функцию pickAUrl. Эта функция принимает число в качестве входных данных, заменяет его двумя и выбирает один из двух URL-адресов. Ничего особенного.

Функция _populatePromisesArray создает 10 записей в массиве _allPromises, чередуя URL-адреса.

И, наконец, мы пришли к функции сокращения.

Как обычно, выполняется итерация по коллекции - в данном случае this._allPromises. Он принимает три значения:

  1. Предыдущее обещание.
  2. Текущее обещание.
  3. Индекс. В этом примере это используется только для выхода из консоли.

Наконец, он инициализирует первый результат как this. $ Q.when (true). Это просто метод Angular, который немедленно определяет значение (в данном случае true). Другие библиотеки делают это иначе. Вам просто нужно иметь одно обещание, как вещь, которая положит начало процессу с успешным решением.

Здесь происходит настоящее волшебство:

Во-первых, имейте в виду, что самое первое «previousPromise» разрешается автоматически. Это угловатая вещь, $ q.when (true) - разрешается немедленно и передает значение true в код, улавливающий разрешение. При первом обходе это предыдущее обещание. Поскольку он ожидает решения и получает его от вызова when (), он переходит прямо в свой then ().

Логика «затем», представленная как лямбда-функция «() =› {…} », сама возвращает результат вызова currentPromise, передачи URL-адреса и количества итераций (опять же, для ведения журнала). Напоминаем, что это обещаниеFn внизу:

Сам currentPromise немедленно возвращает результат _sequentialPromisesService.GetSomething ().

GetSomething () делает http-вызов и сидит в ожидании ответа. но не раньше, чем он ответит отложенным обещанием.

Между тем, вернувшись к сокращению, он немедленно переходит к следующей записи в массиве, потому что он только что сделал асинхронный вызов GetSomething (). К счастью для нас, он врезается в then () предыдущего обещания. Итак, он ждет. Как только предыдущее обещание разрешается, оно, как акула, переходит к следующей записи в массиве. Это порождает новое возвращенное обещание, сокращает итераций и снова сталкивается с then () предыдущего обещания.

В конце концов это обещание и все остальные исполнятся. Прежде чем вы это узнаете, вы должны последовательно выполнить каждый асинхронный вызов. Другими словами, Боб твой дядя.

В итоге

Это еще один пример, когда слово «уменьшить» кажется плохим описанием функциональности. Я думаю, что трудно привести какие-либо аргументы, не говоря уже о разумных аргументах, что мы здесь что-то «сократили».

Я думаю, это очень круто. Reduce при таком использовании обеспечивает краткий и изящный способ управления потоком вашего приложения. Приложив немного воображения и немного поработав, но, вероятно, не так уж много, вы могли бы связать воедино целый ряд асинхронных вызовов, некоторые из которых выполняются параллельно, а некоторые должны быть последовательными. В некотором смысле вы можете преобразовать целые части функциональности
в последовательность связанных обещаний, которые срабатывают в ответ на ввод данных пользователем. Это интересная идея, и я надеюсь ее реализовать.

А пока удачного кодирования!