Как правильно вернуть несколько значений из обещания?


Я недавно столкнулся с определенной ситуацией пару раз, которые я не знал, как решить должным образом. Предположим, что следующий код:

somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( amazingData ) {
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}

теперь может возникнуть ситуация, когда я хотел бы иметь доступ к amazingData in afterSomethingElse.

одним из очевидных решений было бы вернуть массив или хэш из afterSomething, потому что, ну, вы можете возвращать только одно значение из функции. Но мне интересно, есть ли способ иметь afterSomethingElse принять 2 параметра и вызвать его точно так же, как это, кажется, намного проще документ и понять.

мне только интересно об этой возможности, так как есть Q.spread, который делает что-то похожее на то, что я хочу.

8   51   2015-02-24 21:36:43

8 ответов:

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

обещание по своей сути решает с одним значением - это часть того, как работает Q,как работает Promises / A+ spec и как абстрагирование строительство.

в ближайшим вы можете получить это использовать Q.spread и возвращать массивы или использовать ES6 деструктурирование, если оно поддерживается или вы готовы использовать инструмент транспиляции, такой как BabelJS.

что касается передачи контекста вниз по цепочке обещаний, пожалуйста, обратитесь к Берги о том, что.

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

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

somethingAsync().then(afterSomething);

function afterSomething(amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

полностью, а не частично встроенную форму (эквивалентную, возможно, более последовательную):

somethingAsync().then(function (amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

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

function step1(){
  let server = "myserver.com";
  let data = "so much data, very impresive";
  return Promise.resolve([server, data]);
}

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

function step2([server, data]){
  console.log(server); // print "myserver.com"
  console.log(data);   // print "so much data, very impresive"
  return Promise.resolve("done");
}

назвать оба обещания, приковав их цепью:

step1()
.then(step2)
.then((msg)=>{
  console.log(msg); // print "done"
})

две вещи, которые вы можете сделать, вернуть объект

somethingAsync()
    .then( afterSomething )
    .then( afterSomethingElse );

function processAsync (amazingData) {
     //processSomething
     return {
         amazingData: amazingData, 
         processedData: processedData
     };
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}

function afterSomethingElse( dataObj ) {
    let amazingData = dataObj.amazingData,
        processedData = dataObj.proccessedData;
}

использовать рамки!

var amazingData;
somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( returnedAmazingData ) {
  amazingData = returnedAmazingData;
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
  //use amazingData here
}

вот как я считаю, что вы должны делать.

расщепление цепи

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

поскольку в вашем примере выполняется некоторый код, я предположу, что все это объявлено внутри функции. Я назову его toto (). Тогда мы будем иметь еще одна функция, которая будет работать как afterSomething() и afterSomethingElse().

function toto() {
    return somethingAsync()
        .then( tata );
}

вы также заметите, что я добавил возвращение заявление, как это обычно бывает с обещаниями - вы всегда возвращаете обещание, чтобы мы могли продолжать цепочку, если это необходимо. Вот,somethingAsync() будет amazingData и он будет доступен везде внутри новой функции.

теперь, что это новая функция будет выглядеть как обычно зависит от является processAsync () также асинхронным?

processAsync не асинхронный

нет причин усложнять вещи, если processAsync() не асинхронный. Какой-нибудь старый добрый последовательный код сделает это.

function tata( amazingData ) {
    var processed = afterSomething( amazingData );
    return afterSomethingElse( amazingData, processed );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

обратите внимание, что это не важно, если afterSomethingElse() делает что-то async или нет. Если это произойдет, обещание будет возвращено, и цепь может продолжать. Если это не так, то будет возвращено значение результата. Но поскольку функция вызывается из затем(), значение будет завернуто в обещание в любом случае (по крайней мере, в сыром Javascript).

processAsync асинхронный

если processAsync() является асинхронным, код будет выглядеть немного по-другому. Здесь мы рассмотрим afterSomething() и afterSomethingElse() не собираются повторно использоваться в любом месте еще.

function tata( amazingData ) {
    return afterSomething()
        .then( afterSomethingElse );

    function afterSomething( /* no args */ ) {
        return processAsync( amazingData );
    }
    function afterSomethingElse( processedData ) {
        /* amazingData can be accessed here */
    }
}

то же, что и раньше для afterSomethingElse(). Он может быть асинхронным или нет. Обещание будет возвращено или значение, завернутое в разрешенное обещание.


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

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

ест пример

вот пример:

function goingThroughTheEatingProcess(plenty, of, args, to, match, real, life) {
    return iAmAsync()
        .then(chew)
        .then(swallow);

        function chew(result) {
            return carefullyChewThis(plenty, of, args, "water", "piece of tooth", result);
        }

        function swallow(wine) {
            return nowIsTimeToSwallow(match, real, life, wine);
        }
}

function iAmAsync() {
    return Promise.resolve("mooooore");
}

function carefullyChewThis(plenty, of, args, and, some, more) {
    return true;
}

function nowIsTimeToSwallow(match, real, life, bobool) {
}

не фокусируйтесь слишком много на обещание.resolve (). Это просто быстрый способ создать решенное обещание. То, что я пытаюсь достичь этим, - это иметь весь код, который я запускаю в одном месте-прямо под thens. Все остальные функции с более описательным именем можно использовать повторно.

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


использование массивов или объектов, определенных в других ответах, также будет работать. Этот в некотором роде является ответ, предложенный Кевином Ридом.

вы также можете использовать bind () или обещание.все(). Обратите внимание, что они все равно потребуют от вас разделить код.

используя привязать

если вы хотите сохранить ваши функции многоразовые, но на самом деле не нужно держать то, что находится внутри затем очень короткий, вы можете использовать bind ().

function tata( amazingData ) {
    return afterSomething( amazingData )
        .then( afterSomethingElse.bind(null, amazingData) );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

чтобы все было просто,bind () добавит список аргументов (кроме первого) к функции при ее вызове.

используя обещание.все

в своем посте вы упомянули использование spread (). Я никогда не использовал фреймворк используют, но вот как вы должны быть в состоянии использовать его.

некоторые считают обещание.все() - это решение всех проблем, поэтому заслуживает упоминания, я думаю.

function tata( amazingData ) {
    return Promise.all( [ amazingData, afterSomething( amazingData ) ] )
        .then( afterSomethingElse );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( args ) {
    var amazingData = args[0];
    var processedData = args[1];
}

вы можете передать данные обещание.все() - обратите внимание на наличие массива - до тех пор, как обещания, но убедитесь, что ни одно из обещаний не потерпит неудачу в противном случае он остановит обработку.

и вместо определения новых переменных от args аргумент, вы должны быть в состоянии использовать spread () вместо затем() для всех видов удивительной работы.

просто создайте объект и извлеките аргументы из этого объекта.

let checkIfNumbersAddToTen = function (a, b) {
return new Promise(function (resolve, reject) {
 let c = parseInt(a)+parseInt(b);
 let promiseResolution = {
     c:c,
     d : c+c,
     x : 'RandomString'
 };
 if(c===10){
     resolve(promiseResolution);
 }else {
     reject('Not 10');
 }
});
};

извлеките аргументы из promiseResolution.

checkIfNumbersAddToTen(5,5).then(function (arguments) {
console.log('c:'+arguments.c);
console.log('d:'+arguments.d);
console.log('x:'+arguments.x);
},function (failure) {
console.log(failure);
});

все, что вы вернете из обещания, будет завернуто в обещание, которое будет развернуто в следующий раз

вы можете проверить наблюдаемых в лице Rxjs, позволяет вернуть более одного значение.