Яка різниця між майбутнім та обіцянкою?


274

Яка різниця між Futureі Promise?
Вони обидва виступають як заповнювач для майбутніх результатів, але де головна відмінність?


99
Ви можете зробити це, Promiseі ви дотримуєтесь його. Коли хтось дасть вам обіцянку, ви повинні дочекатися, щоб побачити, чи будуть вони її шануватиFuture
Кевін Райт

1
wikipedia
Майбутнє

30
Одна з найменш корисних статей у Вікіпедії, яку я коли-небудь читав
Fulluphigh

Відповіді:


145

Відповідно до цієї дискусії , Promiseнарешті було закликано CompletableFutureвключити в Java 8, і її javadoc пояснює:

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

Приклад також наведено у списку:

f.then((s -> aStringFunction(s)).thenAsync(s -> ...);

Зауважте, що кінцевий API дещо відрізняється, але дозволяє подібне асинхронне виконання:

CompletableFuture<String> f = ...;
f.thenApply(this::modifyString).thenAccept(System.out::println);

78
Це не ваша вина Ассилія, але цей екстракт javadoc потребує серйозного ремонту гідного автора техніки. На моєму п’ятому читанні я можу просто почати цінувати те, що він намагається сказати ... і до цього я приходжу з розумінням майбутнього та обіцянок!
Буряк-буряк

2
@ Beetroot-Beetroot здається, що це сталося до цього часу.
герман

1
@herman Спасибі - я оновив посилання, щоб вказати на остаточну версію javadoc.
assylias

7
@ Beetroot-Beetroot Ви повинні ознайомитись з документом для виняткового методу. Це було б чудовим віршем, але це винятковий збій читабельної документації.
Fulluphigh

4
Для всіх, хто цікавиться, @Fulluphigh посилається на це . Схоже, це було видалено / капітально відремонтовано на Java 8.
Седрік Райхенбах

148

(Я поки що не задоволений відповідями, тому ось моя спроба ...)

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

Майбутні і обіцянки є досить схожими поняттями, різниця полягає в тому, що майбутнє - це контейнер, доступний лише для читання, для результату, який ще не існує, тоді як обіцянку можна написати (як правило, лише один раз). Java 8 CompletableFuture та Guava SettableFuture можна вважати обіцянками, оскільки їх значення можна встановити ("завершено"), але вони також реалізують інтерфейс Future, тому для клієнта різниці немає.

Результат майбутнього буде встановлений «кимось іншим» - результатом асинхронного обчислення. Зауважте, як FutureTask - класичне майбутнє - має бути ініціалізовано за допомогою Callable або Runnable, не існує конструктора без аргументів, і Future і FutureTask читаються лише зовні (встановлені методи FutureTask захищені). Значення буде встановлено в результаті обчислення зсередини.

З іншого боку, результат обіцянки може бути встановлений "ви" (або насправді ким-небудь) будь-коли, оскільки він має публічний метод встановлення. І CompletableFuture, і SettableFuture можна створити без будь-якого завдання, і їх значення можна встановити в будь-який час. Ви відправляєте обіцянку до клієнтського коду та виконуєте його пізніше, як хочете.

Зауважте, що CompletableFuture не є "чистою" обіцянкою, її можна ініціалізувати із завданням так само, як FutureTask, а його найкориснішою особливістю є непов'язане ланцюжок кроків обробки.

Також зауважте, що обіцянка не повинна бути підтипом майбутнього, і вона не повинна бути тим самим об’єктом. В Scala a Future об’єкт створюється асинхронним обчисленням або іншим об'єктом Promise. У C ++ ситуація схожа: об'єкт обіцянки використовується виробником, а майбутній об’єкт споживачем. Перевага цього розділення полягає в тому, що клієнт не може встановити значення майбутнього.

І Spring, і EJB 3.1 мають клас AsyncResult, схожий на обіцянки Scala / C ++. AsyncResult реалізує майбутнє, але це не справжнє майбутнє: асинхронні методи у Spring / EJB повертають інший об'єкт майбутнього, доступний лише для читання, завдяки деякій фоновій магії, і це друге "реальне" майбутнє може бути використане клієнтом для отримання результату.


116

Я усвідомлюю, що відповідь вже прийнята, але хотів би додати свої два центи:

TLDR: Майбутнє та Обіцяння - це дві сторони асинхронної операції: споживач / абонент та виробник / виконавець .

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

Тепер подумайте, як реально реалізується цей метод API: Реалізатор повинен Futureнегайно повернутися . Вони несуть відповідальність за завершення цього майбутнього, як тільки буде проведено обчислення (про що вони будуть знати, оскільки він реалізує логіку відправлення ;-)). Вони використовуватимуть a Promise/ CompletableFutureto робити саме так: Побудуйте та поверніть CompletableFutureнегайно та зателефонуйте, complete(T result)коли обчислення виконано.


1
Чи означає це, що Обіця - це завжди підклас Майбутнього і що писемність Майбутнього просто затьмарена типом?
devios1

Я не думаю, що це мається на увазі . Хоча це дуже часто реалізується (наприклад, у Java, Scala).
Рахель Люті

74

Я наведу приклад того, що таке Promise і як її значення можна було встановити в будь-який момент, на відміну від Future, яке значення є лише читабельним.

Припустимо, у вас є мама, і ви просите у неї грошей.

// Now , you trick your mom into creating you a promise of eventual
// donation, she gives you that promise object, but she is not really
// in rush to fulfill it yet:
Supplier<Integer> momsPurse = ()-> {

        try {
            Thread.sleep(1000);//mom is busy
        } catch (InterruptedException e) {
            ;
        }

        return 100;

    };


ExecutorService ex = Executors.newFixedThreadPool(10);

CompletableFuture<Integer> promise =  
CompletableFuture.supplyAsync(momsPurse, ex);

// You are happy, you run to thank you your mom:
promise.thenAccept(u->System.out.println("Thank you mom for $" + u ));

// But your father interferes and generally aborts mom's plans and 
// completes the promise (sets its value!) with far lesser contribution,
// as fathers do, very resolutely, while mom is slowly opening her purse 
// (remember the Thread.sleep(...)) :
promise.complete(10); 

Результатом цього є:

Thank you mom for $10

Обіцянка мами була створена, але чекала на якусь подію "завершення".

CompletableFuture<Integer> promise...

Ви створили таку подію, прийнявши її обіцянку та оголосивши свої плани подякувати мамі:

promise.thenAccept...

У цей момент мама почала відкривати гаманець ... але дуже повільно ...

а батько втрутився набагато швидше та виконав обіцянку замість вашої мами:

promise.complete(10);

Ви помітили виконавця, про який я писав прямо?

Цікаво, що якщо ви замість цього використовуєте неявний виконавець за замовчуванням (commonPool), а батька немає вдома, а лише мама зі своїм «повільним гаманцем», то її обіцянка виконається лише в тому випадку, якщо програма живе довше, ніж мамі потрібно отримати гроші від гаманець.

Виконавець за замовчуванням діє на зразок "демона" і не чекає, коли всі обіцянки будуть виконані. Я не знайшов хорошого опису цього факту ...


8
Це так весело читати цей! Я не думаю, що більше можу забути майбутнє та обіцяти.
користувач1532146

2
Це потрібно сприйняти як відповідь. Це так само, як читання історії. Дякую @Vladimir
Phillen

Дякуємо @Vladimir
intvprep

9

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

Наприклад, подивіться, як визначаються обіцянки в javascript:

https://promisesaplus.com/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Основна увага приділяється компостуванню за допомогою такого thenметоду:

asyncOp1()
.then(function(op1Result){
  // do something
  return asyncOp2();
})
.then(function(op2Result){
  // do something more
  return asyncOp3();
})
.then(function(op3Result){
  // do something even more
  return syncOp4(op3Result);
})
...
.then(function(result){
  console.log(result);
})
.catch(function(error){
  console.log(error);
})

що робить асинхронні обчислення схожими на синхронні:

try {
  op1Result = syncOp1();
  // do something
  op1Result = syncOp2();
  // do something more
  op3Result = syncOp3();
  // do something even more
  syncOp4(op3Result);
  ...
  console.log(result);
} catch(error) {
  console.log(error);
}

що досить класно. (Не настільки круто, як async-await, але async-await просто видаляє котельну панель .... тоді (функція (результат) {.... з неї).

І насправді їх абстрагування досить добре, як конструктор обіцянок

new Promise( function(resolve, reject) { /* do it */ } );

дозволяє надати два зворотні виклики, які можна використати для Promiseуспішного або помилкового завершення. Так що лише код, який будує, Promiseможе завершити його, і код, який отримує вже сконструйований Promiseоб'єкт, має вигляд лише для читання.

З успадкуванням вищезазначеного можна досягти, якщо способи вирішення та відхилення є захищеними методами.


4
+1. Це правильна відповідь на це питання. CompletableFutureможе мати деяку схожість з Promiseале, але це все ще не єPromise , тому що спосіб його споживання інший: Promiseрезультат вживається за допомогою виклику then(function), а функція виконується в контексті виробника відразу після виклику виробника resolve. FutureРезультат «s споживається викликом , getякий викликає споживчу нитка чекати , поки виробник потік не генерується значення, а потім обробляє його в споживача. Futureза своєю суттю багатопоточне, але ...
Періата Бреата

5
... цілком можливо використовувати Promiseлише один потік (а насправді це саме те середовище, для якого вони були спочатку розроблені: програми javascript зазвичай мають лише один потік, тому ви не можетеFuture там їх реалізувати ). PromiseТому набагато легше і ефективніше Future, але Futureможе бути корисним у складних ситуаціях та потребує співпраці між потоками, які неможливо легко впорядкувати за допомогою Promises. Підводячи підсумок: Promiseце Futureпоштова модель, а модель - тяга (пор. Iterable vs Observable)
Periata Breatta

@PeriataBreatta Навіть у середовищі з однією ниткою повинно бути щось, що виконує обіцянку (що, як правило, відбувається як інший потік, наприклад, an XMLHttpRequest). Я не вірю в твердження про ефективність, чи трапляються у вас якісь цифри? +++ Це сказало, дуже приємне пояснення.
maaartinus

1
@maaartinus - так, щось повинно виконати обіцянку, але це може (і насправді це в багатьох випадках) зробити за допомогою циклу верхнього рівня, який опитує зміни у зовнішньому стані та вирішує те, що обіцянки стосуються дій, які закінчилися. Ефективність, я не маю чітких цифр для Обіцянок конкретно, але зауважте, що для виклику getневирішених Futureобов'язково будуть входити 2 потокові контекстні комутатори, що, принаймні, через кілька років тому, ймовірно, вимагатиме близько 50 нас .
Periata Breatta

@PeriataBreatta Насправді ваш коментар повинен бути прийнятим рішенням. Я шукав пояснення (тягнути / натиснути, одно / багатопотокове), як твоє.
Тома Якоб

5

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


2

У інтерфейсі майбутнього немає встановленого методу, отримуйте лише метод, тому він доступний лише для читання. Про CompletableFuture, ця стаття може бути корисною. повноцінне майбутнє

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.