Используя собственные промисы Javascript, разрешайте промис после прикрепления значков

Есть ли способ использовать собственные промисы javascript (документы) создать промис и прикрепить потомки, не зная во время конструктора, как это разрешится?

var foo = new Promise(function(resolve, reject) {
    // I don't know how this will resolve yet as some other object will resolve it
});

foo.then(function(val) {
  console.log("first " + val);
});

foo.resolve("bar");

foo.then(function(val) {
  console.log("second " + val);
});

// result
// first bar
// second bar

person Nucleon    schedule 18.02.2014    source источник
comment
Если это анти-шаблон, то $(function() {} в jQuery является анти-шаблоном, так как я прошу ту же функциональность, за исключением того, что это только одно событие (дом готов), он может быть основан на специальных событиях: функции регистрации для вызова по событию, если функции зарегистрированы после вызова события, они будут вызываться сразу с теми же аргументами. Если вы можете найти способ сделать это без анти-шаблона, пожалуйста, поделитесь им. Просто сказать, что вам это на самом деле не нужно, никому не поможет, и это все равно, что сказать, что нам на самом деле не нужно DOM ready.   -  person Nucleon    schedule 03.05.2014


Ответы (2)


Просто сохраните их внутри замыкания.

var makePromise = function () {
    var resolvePromise = null,
        rejectPromise  = null,

        promise = new Promise(function (resolve, reject) {
            resolvePromise = resolve;
            rejectPromise  = reject;
        });

    return { promise : promise, resolve : resolvePromise, reject : rejectPromise };
};


var deferredSomething = function () {
    var deferredThing = makePromise();
    waitAWhile()
        .then(doStuff)
        .then(function (result) {
            if (result.isGood) {
                deferredThing.resolve(result.data);
            } else {
                deferredThing.reject(result.error);
            }
        });

    return deferredThing.promise;
};

На самом деле это большая часть различий между концепцией «отложенный» и концепцией «обещание»; еще один уровень сверху, на котором есть фактические пульты дистанционного управления, которые вы можете передать кому-то еще, в то время как вы передаете .then|.success|.done|etc... своим потребителям.

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

ОБНОВИТЬ

Учитывая, что этот ответ, вероятно, по-прежнему будет выбранным ответом и по-прежнему будет отвергаться как решение точной проблемы, с которой он столкнулся (то есть: модификация кода, который не был сделан с учетом обещаний ES6), я полагаю, что я добавим более подробный пример того, почему выборочное использование этого антипаттерна может быть лучше, чем ничего:

MongoClient.connect("mongodb://localhost:21017/mydb", (err, db) => {
    db.collection("mycollection", (err, collection) => {
        collection.find().toArray((err, results) => {
            doStuff(results);
        });
    });
});

Если бы я написал библиотеку здесь, надеясь достичь точки, в которой я мог бы написать:

let dbConnected = MongoClient.connect(dbURL);

dbConnected
    .then(db => db.collection(myCollection))
    .then(collection => collection.find(query))
    .then(stream => doStuff(stream));

... или альтернативно:

composeAsync(
    (stream) => doStuff(stream),
    (collection) => collection.find(query),
    (db) => dbCollection(myCollection)
)(dbConnected);

... для простоты использования в библиотеке, имеет ли смысл обернуть каждое тело функции внутри созданного промиса // find = curry(query, collection) return new Promise(resolve, reject) { /* все тело функции , здесь / / делают много вещей, которые не имеют отношения к разрешению mongo.db.collection.find, но имеют отношение к его вызову */ collection.find(query).toArray( /node-callback/(ошибка, результат) { if (ошибка) { отклонение(ошибка); } else { разрешение(результат); } }); };

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

// find = curry(query, collection)
let resolver = new NodeResolver();
collection.find(query).toArray(promise.resolve);
return resolver.promise;

Да, это антишаблон... ...и тем не менее антишаблон, который требует меньшего количества нажатий клавиш, восстанавливает естественный ход цепочки промисов, устраняет проблему с API-интерфейсом Node только для обратного вызова, снижает вероятность ошибок и т. д. так далее.

Да, уже есть библиотеки, которые делают это... ...решения, специфичные для библиотеки X или Y... ...или решения, которые глобально переопределяют методы различных модулей (страшно) ...или решения, которые, опять же, в основном заставить вас передать все детали звонка, который вы делаете:

wrapNodeMethod(fs, "read", url, config).then(data => { /*...*/ });

Но нет простого решения для обращения всей этой боли без:

а) обертывание всего тела функции в обещание, чтобы передать асинхронный обратный вызов преобразователю; б) использование антипаттерна в библиотеке, чтобы передать обратные вызовы Node в преобразователь, о котором остальная часть тела функции точно ничего не должна знать.

Даже если данные необходимо преобразовать в распознавателе, все равно имеет смысл возвращать преобразованный набор в новом промисе.

let resolver = new NodeResolver();
somethingAsync(resolver.resolve);
return resolver.promise.then(transformData).then(logTransform);

... чем обернуть все тело, включая преобразования и т. д., только для справки по области закрытия, просто ради того, чтобы избежать «антипаттерна», который явно идет вразрез с тем, что стало очень заметной платформой/парадигмой JS.

Лично я был бы более счастлив, если бы методы IO||Node возвращали обещание и/или поток, а также принимали обратный вызов в качестве основной части платформы... ...такого не произойдет. ..

... но вы не можете сказать мне, что писать меньше и оставлять модули Node СУХИМИ, в то же время используя ES6 Promises, является «антипаттерном», поэтому не предоставляя мне более красноречивого решения.

Если вы действительно можете предоставить мне что-то, что я могу использовать в любом обратном вызове NodeJS, что действительно обеспечивает более красноречивое решение этой проблемы, так что мне не нужно оборачивать каждое тело каждый метод, который содержит асинхронный обратный вызов в новом конструкторе, или использует неуклюжие методы диспетчера, или захватывает целые модули, чтобы переопределить их глобальную функциональность...

... Я был бы более чем готов отказаться от своего заявления о том, что этот конкретный шаблон по-прежнему очень полезен в отношении взаимодействия с API, которые склонны к пирамидам o'dewm.

person Norguard    schedule 18.02.2014
comment
Работает отлично. Я переименовал ваш makePromise в makeDeferred в своем собственном коде. Я также убрал лишнее из своего поста, чтобы облегчить его будущим читателям. - person Nucleon; 19.02.2014
comment
Пожалуйста, прекратите использовать промисы, такие как эмиттеры событий, как вы, люди, вообще оправдываете использование промисов, если вы пишете с ними такой код? deferredSomething должно быть просто return waitAWhile().then(doStuff);, а не этот занятый код, работающий по кругу и ничего не делающий - person Esailija; 20.02.2014
comment
@Esailija, он попросил ответа, особенно с целью модификации старого кода, что явно звучало так, как будто ему нужно было в контексте старого кода отделить логику разрешения от потока кода (в частности, точка его конструкция). И когда у вас есть устаревший модуль на 500 строк, который использует формат, который затрудняет (или делает невозможным) выполнение всех разрешений в конструкторе без рефакторинга, тогда вы получаете именно это. Я не сказал, что это был правильный способ сделать это, просто предложил решение, которое можно модифицировать для того, что он искал. - person Norguard; 23.02.2014
comment
@Norguard нет, я имею в виду, что ваш код похож на написание if (b === true) { return true; } else { return false; }, для которого нет другого объяснения, кроме того, что я не понимаю логические значения. - person Esailija; 23.02.2014
comment
@Esailija хорошо, это нормально. Однако я пытался подчеркнуть, что если он хочет делегировать разрешение обещания процессу, который не (или не может) быть определен в конструкторе (устаревшие/и т. д.), то они могут быть сохранены в родительскую область и экспортировать с обещанием, а затем их можно отдать кому угодно. Их можно даже разрешить отдельными логическими цепочками (не очень хорошая идея). if (true) { success(); } - это просто надуманный пример того, как запустить успех/неудачу вне конструктора. Они могут быть обратными вызовами или любой системой логических вентилей, которую вы хотите построить. - person Norguard; 23.02.2014

Если результат обещания зависит от другого обещания, вы должны просто создать обещание, используя then.

То, что было предложено @Norguard в прямой форме, не имеет особого смысла (это даже придумано как отложенный анти-шаблон). Код ниже делает то же самое, и никаких дополнительных обещаний не требуется:

var deferredSomething = function () {
  return waitAWhile()
      .then(doStuff)
      .then(function (result) {
          if (result.isGood) {
            return result.data;
          } else {
            throw result.error;
          }
       });
  });
};

И даже если по какой-то причине вам нужно будет создать промис заранее, то с шаблоном конструктора было бы чище сделать это так:

var deferredSomething = function () {
  return new Promise(function (resolve, reject) {
    waitAWhile()
      .then(doStuff)
      .then(function (result) {
          if (result.isGood) {
            resolve(result.data);
          } else {
            reject(result.error);
          }
       });
  });
};
person Mariusz Nowak    schedule 19.02.2014
comment
Я не верю, что ваш ответ сработает. Не могли бы вы показать, как вы создадите промис, прикрепите thenables, а затем разрешите его, чтобы thenables получили разрешенное значение? - person Nucleon; 19.02.2014
comment
@MariuszNowak Это анти-шаблон с точки зрения современной спецификации A +. Проблема в том, что до того, как появился A+ (или, точнее, до того, как эта практика была широко понята/принята), много кода было написано с использованием Deferreds, с ожиданием, что разрешить/отклонить можно будет передавать как управляющие операторы. . Примером этого может быть отсрочка загрузки виджета до тех пор, пока взаимодействие с пользователем не загрузит его, однако наличие существующих в настоящее время виджетов, подписавшихся на него, если он когда-либо появится, или сращивание выполнено/сбой/ разрешать/отклонять ветки. Помещать все это в монолитный конструктор некрасиво. - person Norguard; 19.02.2014
comment
@MariuszNowak, и хотя промисы в качестве ветвящегося потока управления не получили широкого распространения, а скорее используются в качестве каналов, все еще есть код, который хотел бы использовать эту функциональность (без необходимости полного рефакторинга). - person Norguard; 19.02.2014
comment
@Nucleon Это будет работать, если вы поместите все свои асинхронные вещи внутрь конструктора. Идея thenables в библиотеках, совместимых с A+, заключается в том, что вы создаете канал с помощью .then. Для этого вы выполняете разрешение/отклонение в конструкторе и используете метод .then. Вещи внутри конструктора не должны быть разрешены до того, как вы начнете привязывать к ним вещи. Что должно произойти, так это то, что вся логика для определения как разрешать должна находиться внутри конструктора. - person Norguard; 19.02.2014
comment
Анти-шаблон @Norguard не пренебрегает построением промисов с отложенной функцией, но при построении устаревших обещаний вы можете сделать ту же ошибку с конструктором промисов. Посмотрите внимательно на мой пример. Он называется отложенным анти-шаблоном, так как когда он был придуман, построение неразрешенных промисов в большинстве библиотек выполнялось с помощью отложенной функции (и это все еще так). - person Mariusz Nowak; 19.02.2014
comment
@Nucleon, вы можете обернуть свою логику в функцию, которая передается конструктору обещаний. Тем не менее, проблема в том, что у вас нет доступа к фактическому промису (некоторые предлагают, чтобы он был доступен через this, и я надеюсь, что так оно и будет). Чтобы сказать больше, мне нужно увидеть реальный вариант использования, с которым вы боретесь. Это не первый такой запрос, который я вижу, он просто показывает, что предложенный шаблон конструктора не так хорош, как кажется. - person Mariusz Nowak; 19.02.2014
comment
Конкретная причина, по которой мне нужен этот так называемый анти-шаблон, который является спорным, заключается в семантике, аналогичной событию DOM ready, которое может быть запущено только один раз. Если вы зарегистрируетесь до триггера, он будет выполняться по триггеру. Если вы зарегистрируетесь после триггера, он все равно будет выполняться. Возможно, событие никогда не будет запущено. Из-за того, что функции регистрируются в событии задолго до того, как оно будет запущено, просто нет возможности заблаговременно передать путь разрешения. Антипаттерн или нет, я думаю, что это полезный инструмент. - person Nucleon; 19.02.2014
comment
@Nucleon Anti-pattern — это когда вам определенно не нужно этого делать. Есть случаи, когда вам нужно создавать неразрешенные промисы вручную, например. при разрешении значений из потоков, и это прекрасно. Однако, если вы разрешаете значения из потока, уже созданного с помощью обещаний (как в примере @Norguard), и создаете неразрешенное обещание для его разрешения с ожидаемым значением, то это неправильно, поскольку вам не нужно этого делать. - это отложенный анти-шаблон, который я хотел указать (просто использование функции deferred совершенно нормально, если это оправдано, и это ни в коем случае не анти-шаблон). - person Mariusz Nowak; 19.02.2014
comment
@Nucleon, просто возвращающий обещание о готовности DOM, также сработает. Этот ответ единственный разумный. Другой ответ продвигает анти-шаблон. - person Benjamin Gruenbaum; 24.03.2014