Що таке явна обіцянка будівництва антипатернів і як я цього уникаю?


516

Я писав код, який робить щось таке:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

Хтось сказав мені, що це називається " відкладений антипатерн " або " Promiseконструктор антипатерн " відповідно, що поганого в цьому коді і чому його називають антипатерн ?


Чи можу я підтвердити, що забирати це потрібно (у контексті правого, а не лівого, прикладу), видалити getStuffDoneобгортку функції та просто використати буквар Promise?
Дембінський

1
чи маючи catchблок в getStuffDoneобгортці антипатерн?
Дембінські

1
Принаймні, для рідного Promiseприкладу, у вас також є непотрібні обгортки функцій для .thenта .catchоброблювачів (тобто це просто може бути .then(resolve).catch(reject).) Ідеальний шторм анти-шаблонів.
Ной Фрейтас

6
@NoahFreitas цей код написаний таким чином для дидактичних цілей. Я написав це запитання та відповідь, щоб допомогти людям, які стикаються з цим питанням, прочитавши багато коду, виглядаючи так :)
Бенджамін Груенбаум

Дивіться також stackoverflow.com/questions/57661537/… про те, як усунути не лише явну побудову обіцянок, а й використання глобальної змінної.
Девід Спектор

Відповіді:


357

Відклали антипаттерн (тепер явно-будівельна антипаттерн) придуманий Esailija є загальними антипаттерн людей , які є новими для обіцянки зробити, я зробив це саме , коли я вперше використав слово. Проблема з наведеним вище кодом полягає в тому, що він не використовує той факт, що обіцяє ланцюжок.

Обіцяння можуть зв'язатись, .thenі ви можете повернути обіцянки безпосередньо. Ваш код у getStuffDoneможна переписати як:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

Обіцянки полягають у тому, щоб зробити асинхронний код більш читабельним і поводитись як синхронний код, не приховуючи цього факту. Обіцяння являють собою абстракцію над значенням одноразової операції, вони абстрагують поняття висловлювання чи виразу мовою програмування.

Ви повинні використовувати відкладені об'єкти лише тоді, коли ви перетворюєте API в обіцянки і не можете це зробити автоматично або коли ви пишете функції агрегації, які легше виражаються таким чином.

Цитуючи Esailija:

Це найпоширеніший антидіапазон. Легко впасти в це, коли ти не дуже розумієш обіцянки і не вважаєш їх прославленими випромінювачами подій або утилітою зворотного виклику. Давайте резюмуємо: обіцянки про те, щоб асинхронний код зберігав більшість втрачених властивостей синхронного коду, таких як плоский відступ та один канал виключення.


@BenjaminGruenbaum: Я впевнений у використанні відкладених заходів для цього, тому не потрібно в новому питанні. Я просто подумав, що це було корисним випадком, коли ви пропустили свою відповідь. Те, що я роблю, здається більше схожим на агрегацію, чи не так?
mhelvens

1
@mhelvens Якщо ви вручну розділяєте API без зворотного виклику в API для обіцянок, який відповідає частині "перетворення API зворотного виклику в обіцянки". Антипаттерн має на меті обіцяти обіцянку в іншу обіцянку без поважних причин, ви не обертаєте обіцянку почати з того, щоб це не стосувалося тут.
Бенджамін Груенбаум,

@BenjaminGruenbaum: Ах, я хоч і відкладені самі по собі вважалися анти-зразком, що з блакитною пташкою знецінює їх, і ви згадуєте про "перетворення API на обіцянки" (що також є випадком, коли не потрібно обіцяти спочатку).
mhelvens

@mhelvens Я думаю, що надлишковий відкладений антидіаграма буде точнішим для того, що він насправді робить. Bluebird знецінив .defer()api в більш новій (і кидають у безпеку) конструкторі обіцянок, він (жодним чином) не принизив поняття побудови обіцянок :)
Бенджамін Груенбаум,

1
Дякую @ Roamer-1888, твоя довідка допомогла мені нарешті з’ясувати, що було моєю проблемою. Схоже, я створював вкладені (неповернені) обіцянки, не усвідомлюючи цього.
ghuroo

134

Що не так з ним?

Але візерунок працює!

Щасливчик. На жаль, це, мабуть, не так, як ви, ймовірно, забули якийсь крайній випадок. Більше половини випадків, які я бачив, автор забув подбати про обробку помилок:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

Якщо інша обіцянка буде відхилена, це станеться непоміченим, замість того, щоб поширюватись на нову обіцянку (там, де вона впорається) - і нова обіцянка залишається назавжди очікуваною, що може викликати витоки.

Те ж саме відбувається в тому випадку, коли ваш код зворотного дзвінка викликає помилку - наприклад, коли resultнемає propertyта викинуто виняток. Це не піде на розгляд, а нова обіцянка залишиться невирішеною.

На противагу цьому, використання .then()автоматично бере на себе обидва ці сценарії та відхиляє нову обіцянку, коли трапляється помилка:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

Відкладений антипатерн не тільки громіздкий, але й схильний до помилок . Використовувати .then()для прикування набагато безпечніше.

Але я впорався з усім!

Дійсно? Добре. Однак це буде досить детально та багато, особливо якщо ви використовуєте бібліотеку обіцянок, яка підтримує інші функції, такі як скасування чи передача повідомлення. А може, це буде в майбутньому, або ви хочете поміняти свою бібліотеку на кращу? Ви не хочете, щоб переписати свій код для цього.

Методи бібліотек (then ) не лише підтримують усі функції, але й можуть мати певні оптимізації. Використання їх, швидше за все, зробить ваш код швидшим або, принаймні, дозволить оптимізуватися майбутніми версіями бібліотеки.

Як я цього уникаю?

Тому щоразу, коли ви виявите, що вручну створюються Promiseабо Deferredвже залучені обіцянки, спочатку перевірте API бібліотеки . Відкладений антипатерн часто застосовується людьми, які бачать обіцянки [лише] як зразок спостерігача - але обіцянки більше, ніж зворотні дзвінки : вони повинні бути композиційними. Кожна порядна бібліотека має безліч простих у використанні функцій для складання обіцянок у будь-який тонкий спосіб, піклуючись про всі матеріали низького рівня, з якими ви не хочете мати справу.

Якщо ви виявили необхідність складати деякі обіцянки по-новому, що не підтримується існуючою допоміжною функцією, написання власної функції з неминучими відкладеннями має бути вашим останнім варіантом. Подумайте про перехід на більш функціональну бібліотеку та / або подайте помилку в поточну бібліотеку. Її обслуговуючий персонал повинен мати можливість отримувати композицію з існуючих функцій, реалізовувати нову функцію помічника для вас та / або допомагати виявляти крайні випадки, з якими потрібно працювати.


Чи є інші приклади, крім функції, в тому числі setTimeout, де конструктор може бути використаний, але не вважатися "Обіцяючим конструктором"?
гість271314

1
@ guest271314: все асинхронне, що не повертає обіцянки. Хоча досить часто ви отримуєте кращі результати завдяки спеціалізованим помічникам бібліотек щодо розвідування. І переконайтеся , що завжди promisify на найнижчому рівні, так що це не « функція в тому числіsetTimeout », але « функція setTimeoutсама по собі ».
Бергі

"І переконайтеся, що завжди обіцяйте на найнижчому рівні, тому це не" функція, що включає setTimeout", а" сама функція setTimeout"" Чи можете описати, посилатися на відмінності між двома?
гість271314

@ Guest271314 функція , яка включає в себе тільки виклик setTimeoutявно відрізняється від функції setTimeoutсамого , чи не так?
Бергі

4
Я думаю, що один із важливих уроків, який до цього часу не був чітко прописаний, - це те, що Обіцяння та його ланцюжок «тоді» являє собою одну асинхронну операцію: початкова операція знаходиться в конструкторі «Обіцяння», а кінцева кінцева точка - у « тоді 'функція. Тож якщо у вас є операція синхронізації, за якою слідує операція асинхронізації, покладіть речі синхронізації в Обіцяння. Якщо у вас є операція з асинхронізацією, за якою слідує синхронізація, покладіть дані синхронізації у поле "тоді". У першому випадку поверніть оригінал Обіцянки. У другому випадку поверніть ланцюжок Promise / then (що також є Обіцянкою).
Девід Спектор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.