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

86

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

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

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

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

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

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

Der Hochstapler
источник

Ответы:

88

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

Обещание по сути разрешается с помощью одного значения - это часть того, как работает Q, как работает спецификация Promises / A + и как работает абстракция .

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

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

Бенджамин Грюнбаум
источник
16
Что плохого в разрешении с помощью объекта, имеющего несколько свойств? Похоже на простой способ получить несколько значений из разрешения.
jfriend00,
6
Это прекрасно,
Бенджамин Грюнбаум,
Вы также можете расширить Promise, чтобы .spread()в соответствующем ответе было показано , как Bluebird: stackoverflow.com/a/22776850/1624862
Кевин Гадьяни,
Поведение Promise.all (), кажется, противоречит этому. Promise.all([a, b, c]).then(function(x, y, z) {...})правильно работает во всех современных движках Javascript, где x, y и z оцениваются в разрешенные значения a, b и c. Так что точнее будет сказать, что язык не позволяет вам делать это легко (или разумно) из пользовательского кода (потому что вы можете вернуть Promise непосредственно из предложения then, вы можете заключить свои значения в обещания, а затем заключить их в Promise .all (), чтобы получить желаемое поведение, хотя и запутанным образом).
Остин Хеммельгарн 01
3
@AustinHemmelgarn Это просто ложь, Promise.allотрабатывает с массивом . В Promise.all([a,b]).then((a, b) => bесть undefined. Вот почему вам нужно выполнить .then(([a, b]) =>это деструктурирующее задание,
Бенджамин Грюнбаум
38

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

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"
})
Алехандро Силва
источник
5
Это называется деструктуризация , а не «деконструктор», и это не оператор: - /
Берги
3
Кстати, вы можете использовать деструктуризацию прямо в параметрах: function step2([server, data]) { …- таким образом вы также избегаете назначения неявным глобальным переменным. И вам действительно следует использовать returnили Promise.resolve, а не new Promiseконструктор в своих примерах.
Берги
thans @Bergi за рекомендации!
Алехандро Силва
19

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

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

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
  });
}
Кевин Рид
источник
3
Это нормально вернуть thenвнутрь другого then? Это не антипаттерн ?
robe007
как сказал @ robe007, разве это не похоже на «ад обратного вызова»? здесь ваше вложение затем блокируется вместо функций обратного вызова, это нарушит саму цель обещаний
Дирадж
5

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

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
}
джемило
источник
3

Вот как, я думаю, вы должны поступать.

разрыв цепи

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

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

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

Вы также заметите, что я добавил оператор return, поскольку это обычно способ использовать Promises - вы всегда возвращаете обещание, поэтому мы можем продолжить цепочку при необходимости. Здесь 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 () что-то асинхронно или нет. Если это так, обещание будет возвращено, и цепочка может продолжаться. Если это не так, будет возвращено значение результата. Но поскольку функция вызывается из then () , значение в любом случае будет заключено в обещание (по крайней мере, в необработанном 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 года. Я не большой поклонник анонимных функций везде. Мне трудно читать. Даже если это довольно распространено в обществе. Это , как мы заменили обратный вызов-ад пути в обещании-чистилище .

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

Пример еды

Вот пример:

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) {
}

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

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


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

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

используя привязку

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

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

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

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

используя Promise.all

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

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

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];
}

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

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

Габриэль
источник
3

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

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');
 }
});
};

Вытяните аргументы из обещания.

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);
});

источник
2

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

Это становится интересным, когда вам нужно вернуть одно или несколько обещаний вместе с одним или несколькими синхронными значениями, такими как;

Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4])
       .then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);

В этих случаях было бы важно использовать Promise.all()get p1и p2promises на следующем .then()этапе, например

Promise.resolve(Promise.all([Promise.resolve(1), Promise.resolve(2), 3, 4]))
       .then(([p1,p2,n1,n2]) => /* p1 is 1, p2 is 2, n1 is 3 and n2 is 4 */);
Уменьшить
источник
1

Вы можете проверить Observable, представленный Rxjs , позволяет вам возвращать более одного значения.

codelovesme
источник
0

Просто верните кортеж:

async add(dto: TDto): Promise<TDto> {
console.log(`${this.storeName}.add(${dto})`);
return firebase.firestore().collection(this.dtoName)
  .withConverter<TDto>(this.converter)
  .add(dto)
  .then(d => [d.update(this.id, d.id), d.id] as [any, string])
  .then(x => this.get(x[1]));

}

swissmawi
источник