Каков правильный шаблон для Promise.defer?

Я использую TypeScript и async/await для представления асинхронного рабочего процесса. Частью этого рабочего процесса является обращение к веб-воркеру и продолжение, когда он перезвонит с результатом.

В C# я бы создал TaskCompletionSource, await его Task, а в другом месте кода вызвал бы SetResult для разрешения TaskCompletionSource.

Я могу сделать то же самое в JavaScript, инициализируя объект Deferrer с помощью Promise.defer(), awaiting его Promise и в другом месте, в window.onmessage слушатель вызовет метод resolve (или reject), чтобы продолжить асинхронный рабочий процесс.

Звучит красиво, но MDN говорит, что defer устарело. Предлагаемое решение с использованием конструктора Promise, который берет на себя работу делегата и вызывает методы resolve/reject, не работает для меня, потому что эта логика может быть вне моей досягаемости, я просто хочу, чтобы объект вызывал resolve или reject на в совершенно другом лексическом объеме, я не могу сделать это с этой функцией.

Существует совместимый помощник в прямом и обратном направлении который дает мне такой объект, связывая функции resolve и reject, которые я могу использовать без изменения семантики моего кода. Но разве это плохая практика? Есть ли признанный, лучший образец? Какой идиоматический эквивалент TaskCompletionSource в JavaScript?


person Tomáš Hübelbauer    schedule 01.08.2016    source источник


Ответы (1)


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

Кажется, ситуация, которую я обнаружил, и та, на которую указывали другие, возникает, когда у вас есть разделение между кодом, который создает обещанный/отложенный объект, и кодом, который разрешает или отклоняет его. Обычно они у вас есть вместе, но бывают случаи, когда это совершенно разные участки кода и есть логические причины делать это именно так. В таких случаях отложенный объект, построенный на основе обычного промиса, может быть более чистым методом реализации.

Вот еще одна простая реализация Deferred, которая также совместима как с предыдущими, так и с предыдущими версиями:

Почему конструктору Promise нужен исполнитель?

Но разве это плохая практика?

Я знаю, что есть некоторые пуристы, которые думают, что вы никогда не должны этого делать. Но я бы сказал, что это отличная практика, если вы уверены, что это самый чистый и простой способ реализации вашего кода и что его реализация с помощью типичного обратного вызова исполнителя конструктора Promise не так практична. Есть те, кто хочет использовать deferred, даже не попробовав и не изучив конструктор/исполнитель промисов, и это не будет хорошей практикой. По ряду причин следует использовать конструктор промисов, когда это целесообразно.

Одна из важных причин, по которой конструктор промисов предпочтительнее, заключается в том, что весь специфичный для промисов код в функции обратного вызова исполнителя "безопасен для бросков". Если в этом коде возникнет исключение, оно будет автоматически перехвачено и отклонено обещание (что хорошо). Если вы используете deferred и у вас есть куча кода, который манипулирует обещанием вне этого обратного вызова, он автоматически не становится безопасным. На самом деле, ваш код может даже генерировать синхронно, что является кошмаром для защиты от вызывающих. Вы можете увидеть описание этой проблемы здесь: https://stackoverflow.com/a/37657831/816620. Таким образом, отложенный шаблон не является предпочтительным, но при надлежащей защите все еще есть некоторые случаи (обычно редкие), когда он приводит к более чистой реализации.

Есть ли признанный, лучший образец?

Практически тот же ответ, что и выше. Предпочтение состоит в использовании конструктора/исполнителя промисов, но в ситуациях, когда это нецелесообразно (обычно когда разные участки кода создают промис, а затем разрешают/отклоняют его) и нет простого способа реорганизовать код, чтобы он хорошо работал с конструктор/исполнитель обещания, то отложенный объект, вероятно, является предпочтительной реализацией. Как правило, такие ситуации встречаются редко, потому что большую часть времени вы можете структурировать свой код так, чтобы один и тот же фрагмент кода создавал и разрешал/отклонял обещание, и вам не нужен отложенный.

Что такое идиоматический эквивалент TaskCompletionSource в JavaScript?

В Javascript нет встроенного эквивалента. Таким образом, вам придется создавать свои собственные, и кажется, что промисы — естественный способ сделать это, поскольку они естественным образом созданы для сигнализации о завершении или ошибке. Вам нужно будет показать нам свой фактический код, чтобы мы могли высказать мнение о том, можно ли реализовать вашу конкретную ситуацию без использования отложенного объекта.

person jfriend00    schedule 01.08.2016
comment
Это потрясающий ответ, спасибо, что потратили на него столько времени. Я не могу представить, как бы я создал Promise для ожидания в главном окне и resolve его из веб-воркера (не меньше другого файла!) при использовании исполнителя конструктора Promise, поэтому я думаю Я в порядке, используя Deferred для этого. - person Tomáš Hübelbauer; 02.08.2016
comment
@ TomášHübelbauer - Ну, webWorker в конечном итоге должен будет отправить сообщение в основной поток, чтобы связаться с ним, верно? Не могли бы вы заставить исполнителя обещания прослушивать это сообщение и разрешать обещание в исполнителе, когда он получает сообщение? Я просто предполагаю, потому что я не видел вашего реального кода. - person jfriend00; 02.08.2016
comment
Если подумать, я мог бы прослушать сообщение в реализации исполнителя, хотя в настоящее время мой процессор сообщений находится в центральном месте, и таким образом мне пришлось бы разбивать его на каждого исполнителя. Поиграю, может что-то упускаю. Отправлю вопрос, если у меня нет идей или я не уверен. :) - person Tomáš Hübelbauer; 02.08.2016
comment
@TomášHübelbauer - Часто (хотя и не всегда) шаблон проектирования исполнителя просто требует переосмысления структуры кода, чтобы использовать его. Удачи. - person jfriend00; 02.08.2016
comment
Я всегда избегал этого, потому что создавал функцию callback в каждой функции. jshint предостерегает от динамического создания функций. Разве это не большая проблема с производительностью? Поэтому лучше просто использовать new Promise(callback)? - person Noitidart; 19.08.2016
comment
@Noitidart - На мой взгляд, jshint заставляет вас избегать лучшего способа кодирования. Эти программы проверки кода на самом деле довольно тупые. Если вы будете слепо делать то, что они говорят, в некоторых случаях вы будете отходить от лучших практик. Вы должны знать, что вы делаете и когда их игнорировать. Анонимные встроенные функции невероятно полезны в Javascript и, безусловно, являются лучшим способом написания кода. Я бы не стал их избегать. jsHint, вероятно, жалуется только в том случае, если вы делаете это в цикле, и в этом случае мне нужно будет увидеть точный код, чтобы узнать, что я бы порекомендовал. - person jfriend00; 19.08.2016