Могу ли я сделать синхронное обещание в библиотеке JavaScript Q?

Я хочу сделать что-то вроде следующего:

delay( 2500 )
  .then( function () { console.log( "Step 1 done" ) } )
  .then( delay( 7500 ) )
  .then( function () { console.log( "Step 2 done" ) } );

Такая реализация задержки была продемонстрирована много раз:

function delay( ms ) {
  var deferred = Q.defer();
  setTimeout( deferred.resolve, ms );
  return deferred.promise;
}

Но если я запустил выше в node.js, я получаю:

... delay of 2500ms
Step 1 done
Step 2 done
... delay of ~7500ms

а не то, что я ожидаю увидеть:

... delay of 2500ms
Step 1 done
... delay of 7500ms
Step 2 done

В примерах, приведенных в https://github.com/kriskowal/q/wiki/Examples-Gallery, я не могу найти примеров синхронных функций (функций, возвращающих значение без каких-либо обратных вызовов), прикованных с функциями обещания.

Любые идеи, как смешивать синхронные действия с асинхронным promises?

Я пробовал:

function synchronousPromise() {
  var deferred = Q.defer();
  console.log( "Synchronous function call" );
  deferred.resolve();
  return deferred.promise;
}

delay( 2500 )
  .then( function(){synchronousPromise()} )
  .then( function(){delay( 7500 )} )
  .then( function(){synchronousPromise()} );

И это выдает:

... delay of 2500ms
Time now is 2013-06-20
Time now is 2013-06-20
... delay of 7500ms

.. все еще не то, что я пытаюсь достичь.

Ответы

Ответ 1

Если вы хотите связать обратные вызовы, вы должны вернуть новый объект обещания из одного из обратных вызовов. В первом примере вы пишете

.then( delay( 7500 ) )

что означает, что вы передаете объект обещания .then, а не функцию. В соответствии с предложением Promise/A + (которое следует Q) все несущественные аргументы должны быть проигнорированы. Итак, в основном это так же, как если бы вы просто пишете:

delay( 2500 )
  .then( function () { console.log( "Step 1 done" ) } )
  .then( function () { console.log( "Step 2 done" ) } );

Вместо этого передайте функцию, которая вызывает delay и возвращает объект-обещание:

delay( 2500 )
  .then( function () { console.log( "Step 1 done" ); } ) 
  .then( function () { return delay( 7500 ); } )
  .then( function () { console.log( "Step 2 done" ); } );

Теперь последний обратный вызов будет вызван только после того, как разрешен объект обещания, возвращенный delay во втором обратном вызове.

Ответ 2

Google привел меня сюда, работая с аналогичной проблемой (но используя Kris Kowal Q), у меня была очень небольшая структура, которая позволяет вам делать следующее:

var chain = [
  doNext(delay, 2500),
  doNext(console.log, "Step 1 done"),
  doNext(delay, 7500),
  doNext(console.log, "Step 2 done")
];

doInOrder(chain);

Структура составляет всего 12 строк и, вероятно, может быть адаптирована для других библиотек обещаний:

var Q = require('q');

function doNext(fn /* , arguments */){
  var args =  Array.prototype.splice.call(arguments, 1);
  return function(prevRetVal){
    // For my needs I didn't need the results from previous fns
    return fn.apply(null, args)
  }
}

function doInOrder(doNexters, init){
  return doNexters.reduce(Q.when, init);
}

Ответ 3

Если вы работаете с Babel или TypeScript, вы можете использовать ES6 Generators:

  'use strict';

  let asyncTask = () =>
    new Promise(resolve => {
      let delay = Math.floor(Math.random() * 1000);

      setTimeout(function () {
        resolve(delay);
      }, delay);
    });

  let makeMeLookSync = fn => {
    let iterator = fn();
    let loop = result => {
      !result.done && result.value.then(res =>
        loop(iterator.next(res)));
    };

    loop(iterator.next());
  };

  makeMeLookSync(function* () {
    let result = yield asyncTask();

    console.log(result);
  });