Створення обіцянки (ES6), не починаючи її вирішувати


77

Як використовувати обіцянки ES6, як мені створити обіцянку, не визначаючи логіки її вирішення? Ось базовий приклад (деякі TypeScript):

var promises = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return promises[key];
  }
  var promise = new Promise(resolve => {
    // But I don't want to try resolving anything here :(
  });

  promises[key] = promise;
  return promise;
}

function resolveWith(key: string, value: any): void {
  promises[key].resolve(value); // Not valid :(
}

Це легко зробити за допомогою інших обіцяних бібліотек. Наприклад, JQuery:

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return deferreds[key].promise();
  }
  var def = $.Deferred();    
  deferreds[key] = def;
  return def.promise();
}

function resolveWith(key: string, value: any): void {
  deferreds[key].resolve(value);
}

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

Дякую.


1
WTH ви б зробили щось подібне? Обіцянка без логіки вирішення - це вічно обіцяна обіцянка.
Бергі,

Ваша друга частина запитання - це дублікат зворотного виклику JavaScript Promise, виконаного Asynchronosuly
Bergi

1
@Bergi - Уявіть щось на зразок асинхронної системи введення залежностей. У вас є одна частина, де ви реєструєте предмети для ін’єкцій, а інша - де ви їх вимагаєте. Якщо я запитую товар, який ще не був зареєстрований, я хочу повернути обіцянку, яка буде вирішена, як тільки вона буде.
Баргуаст,

Відповіді:


91

Гарне питання!

Резолютор, переданий конструктору обіцянки, навмисно працює синхронно для підтримки цього випадку використання:

var deferreds = [];
var p = new Promise(function(resolve, reject){
    deferreds.push({resolve: resolve, reject: reject});
});

Потім, в якийсь пізній момент часу:

 deferreds[0].resolve("Hello"); // resolve the promise with "Hello"

Причиною того, як дається конструктор обіцянок, є те, що:

  • Зазвичай (але не завжди) логіка роздільної здатності пов'язана зі створенням.
  • Конструктор обіцянки є безпечним і перетворює винятки на відхилення.

Іноді він не підходить, і для цього його розподільник працює синхронно. Ось відповідне читання за темою .


Я розмірковував Promise.resolveі зберігав пов'язані then. Але, це виглядає чисто, і я навіть не впевнений, чи зв’язаний thenспрацює.
четверте око

Ого. Дякуємо за неймовірно швидку та ґрунтовну відповідь! У вас трапляється джерело, в якому зазначається, що виконавець (функція з аргументами дозволу та відхилення) завжди працює синхронно під час побудови?
Баргуаст,

1
@Barguast впевнений, для ES6 , зокрема - ecma-international.org/ecma-262/6.0 / ... як обіцянки побудовані, який називає ecma-international.org/ecma-262/6.0/index.html#sec-construct IS викликається синхронно. Це, в свою чергу, викликається синхронно з ecma-international.org/ecma-262/6.0/… - я думаю, що старий github.com/promises-aplus/constructor-spec є кращим джерелом.
Бенджамін Груенбаум,

2
@BenjaminGruenbaum: Просто вкажіть на те, чи виконується JavaScript Promise Callback Asynchronosuly :-)
Бергі

1
@EugeneHoza це було раніше (давно), але його змінили на виявляючий шаблон конструктора (який вважався безпечнішим). Раніше я це любив - але в ретроспективі я не впевнений, що насправді це безпечніше - це просто компроміс. Ви можете прочитати про це тут: blog.domenic.me/the-revealing-constructor-pattern - крім того, якщо ви з пристрастю почуваєтесь про це (звичайно, прочитавши відповідне тло), - дуже радимо зробити пропозиція TC39 ввести інший API.
Бенджамін Груенбаум,

53

Я хочу сюди додати свої 2 центи. Враховуючи саме питання " Створення es6 Promise без початку вирішення ", я вирішив це, створивши функцію обгортки і замість цього викликавши функцію обгортки. Код:

Скажімо, у нас є функція, fяка повертає Promise

/** @return Promise<any> */
function f(args) {
   return new Promise(....)
}

// calling f()
f('hello', 42).then((response) => { ... })

Тепер я хочу підготувати дзвінок, f('hello', 42)не вирішуючи його:

const task = () => f('hello', 42) // not calling it actually

// later
task().then((response) => { ... })

Сподіваюся, це комусь допоможе :)


Посилання Promise.all()на запитання в коментарях (і відповідь @Joe Frambach), якщо я хочу підготувати дзвінок до f1('super')& f2('rainbow'), 2 функції, які повертають обіцянки

const f1 = args => new Promise( ... )
const f2 = args => new Promise( ... )

const tasks = [
  () => f1('super'),
  () => f2('rainbow')
]

// later
Promise.all(tasks.map(t => t()))
  .then(resolvedValues => { ... })

Як це можна використовувати Promise.all()?
Frondor 01.03.18

завдання = [() => f (1), () => f (2)]; Promise.all (tasks.map (t => t ())).
Then

3

Як щодо більш комплексного підходу?

Ви можете написати конструктор, який повертає нову обіцянку, оздоблену .resolve()та .reject()методами.

Ви, мабуть, обрали б назву конструктора Deferred- терміна, що має велику перевагу в [історії] обіцянок javascript.

function Deferred(fn) {
    fn = fn || function(){};

    var resolve_, reject_;

    var promise = new Promise(function(resolve, reject) {
        resolve_ = resolve;
        reject_ = reject;
        fn(resolve, reject);
    });

    promise.resolve = function(val) {
        (val === undefined) ? resolve_() : resolve_(val);
        return promise;//for chainability
    }
    promise.reject = function(reason) {
        (reason === undefined) ? reject_() : reject_(reason);
        return promise;//for chainability
    }
    promise.promise = function() {
        return promise.then(); //to derive an undecorated promise (expensive but simple).
    }

    return promise;
}

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

Крім того, обробляючи fn, шаблон виявлення залишається доступним, якщо вам потрібно / вибрати використовувати його на відкладеному.

ДЕМО

Тепер, коли Deferred()утиліта на місці, ваш код практично ідентичний прикладу jQuery.

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return deferreds[key].promise();
  }
  var def = Deferred();    
  deferreds[key] = def;
  return def.promise();
}

1
Не знаю, здається, це знову вводить все те, чого ми хотіли уникнути, переходячи з відкладених на обіцяні та конструкторський підхід. Крім того, ваша реалізація повністю ускладнена і містить кілька помилок: а) якщо ви Deferredвводите параметр зворотного виклику, його слід викликати назад із відкладеним б) тестування value/ reasonfor undefinedабсолютно непотрібне c) .resolveі .rejectніколи не потрібно прив'язувати d) ваш .promiseметод не повертає неоформленої обіцянки
Бергі,

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

Я маю на увазі, що ви повинні робити fn(promise)(вірніше, fn(deferred)насправді) замість fn(resolve, reject). Так, я, безсумнівно, бачу потребу в "об'єктах-розв'язувачах", які можна десь зберігати та експонувати .fulfillта .rejectметоди, але я думаю, що вони не повинні реалізовувати інтерфейс обіцянки.
Бергі,

@Bergi, я розумію, що jQuery, наприклад, розкриває самого Deferred як аргумент зворотного виклику. Можливо, я чогось пропускаю, але я не розумію, чому відкладена реалізація не повинна імітувати фактичну практику new Promise(fn)виявлення справедливості resolveта reject.
Роумер-1888

Крім того, хоча when.jsдокументація не рекомендує використовувати її when.defer, вона це визнає in certain (rare) scenarios it can be convenient to have access to both the promise and it's associated resolving functions. Якщо вірити цьому твердженню, то те, що я пропоную вище, повинно здатися розумним (у рідкісних випадках).
Роумер-1888

1

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

Promise.unwrapped = () => {
  let resolve, reject, promise = new Promise((_resolve, _reject) => {
    resolve = _resolve, reject = _reject
  })
  promise.resolve = resolve, promise.reject = reject
  return promise
}

// a contrived example

let p = Promise.unwrapped()
p.then(v => alert(v))
p.resolve('test')

Очевидно, раніше був Promise.deferпомічник, але навіть той наполягав на тому, щоб відкладений об'єкт був окремим від самої обіцянки ...


1

Що робить такі питання складними, це процедурний характер Javascript, я думаю. Щоб вирішити цю проблему, я створив простий клас. Ось як це виглядає:

class PendingPromise {

    constructor(args) {
        this.args = args;
    }

    execute() {
        return new Promise(this.args);
    }
}

Ця обіцянка буде виконана лише під час дзвінка execute(). Наприклад:

function log() {
    return new PendingPromise((res, rej) => {
        console.log("Hello, World!");
    });
}

log().execute();

0

CPomise дозволяє вам вирішувати свої обіцянки зовні, але це протимодель, оскільки порушує модель інкапсуляції Promise. ( Демо в реальному часі )

import CPromise from "c-promise2";

const promise = new CPromise(() => {});

promise.then((value) => console.log(`Done: ${value}`)); //123

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