Як правильно повернути кілька значень із обіцянки?


86

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

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

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

Зараз ситуація може виникнути , якщо я хотів би мати доступ до amazingDataв afterSomethingElse.

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

Мені лише цікаво про цю можливість, оскільки існує Q.spread, яка робить щось подібне до того, що я хочу.




Деструктуризація призначення в ES6 допомогла б. перевірити тут
Раві Тея

Відповіді:


88

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

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

Найближче, що ви можете отримати, - це використання Q.spreadта повернення масивів або використання деструктуризації ES6, якщо це підтримується, або ви готові використовувати інструмент трансляції, такий як BabelJS.

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


16
Що поганого в розв’язанні з об’єктом, що має кілька властивостей? Здається, це простий спосіб отримати кілька значень із розв’язання.
jfriend00

6
Це цілком нормально робити
Бенджамін Груенбаум

Ви також можете продовжити обіцянку мати .spread()як Bluebird , показуючи в цьому відповідний відповідь: stackoverflow.com/a/22776850/1624862
Кевін Ghadyani

Здається, поведінка Promise.all () суперечить цьому. Promise.all([a, b, c]).then(function(x, y, z) {...})працює коректно в усіх сучасних механізмах Javascript з x, y та z з оцінкою до дозволених значень a, b та c. Отже, точніше сказати, що мова не дозволяє зробити це легко (або розумно) з коду користувача (оскільки ви можете повернути Promise безпосередньо з пропозиції then, ви можете обернути свої значення в обіцянки, а потім обернути ті, що містять Promise .all (), щоб отримати бажану поведінку, хоча і заплутано).
Остін Хеммелгарн,

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 за рекомендації!
Alejandro Silva

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, чи не буде це схоже на "пекло зворотного виклику"? тут ваше вкладене потім блокує замість функцій зворотного виклику, це перемогло б саму мету обіцянок
Dheeraj

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 , має сенс мати їх у виділеній функції. Зазвичай я роблю це щоразу, коли хочу повторно використовувати деякі дані, тому вони завжди присутні як аргумент функції.

Оскільки у вашому прикладі працює якийсь код, я вважаю, що все це оголошено всередині функції. Я називатиму це toto () . Тоді у нас буде інша функція, яка буде працювати як afterSomething (), так і afterSomethingElse () .

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

Ви також помітите, що я додав заяву про повернення, оскільки зазвичай це спосіб іти в Promises - ви завжди повертаєте обіцянку, щоб ми могли продовжувати ланцюжок, якщо потрібно. Тут щосьAsync () створить дивовижні дані, і це буде доступно всюди в новій функції.

Тепер, як буде виглядати ця нова функція, як правило, залежить від того , що 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 роки. Я не шанувальник анонімних функцій скрізь. Мені важко читати. Навіть якщо це досить поширене явище в громаді. Це як ми замінили зворотний дзвінок пеклом на чистилище обіцянок .

Мені також подобається вказувати назву функцій у тоді короткому. Вони все одно будуть визначені лише локально. І більшу частину часу вони будуть викликати іншу функцію, визначену в іншому місці - настільки багаторазову - для виконання цієї роботи. Я навіть роблю це для функцій лише з 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) {
}

Не зосереджуйтесь надто на Promise.resolve () . Це лише швидкий спосіб створити вирішену обіцянку. Я намагаюся досягти цим, щоб весь код, який я запускаю, знаходився в одному місці - безпосередньо під тенами . Всі інші функції з більш описовою назвою багаторазові.

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


Використання масивів або об’єктів, як визначено в інших відповідях, також спрацювало б. Це певним чином є відповіддю, запропонованою Кевіном Рід .

Ви також можете використовувати bind () або Promise.all () . Зверніть увагу, що вони все одно вимагатимуть розділити ваш код.

за допомогою bind

Якщо ви хочете зберегти свої функції багаторазовим, але вам насправді не потрібно тримати те, що знаходиться всередині, тоді дуже коротким, ви можете використовувати 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

У своєму дописі ви згадали використання поширення () . Я ніколи не використовував фреймворк, який ви використовуєте, але ось як ви повинні мати змогу ним користуватися.

Деякі вважають, що 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');
 }
});
};

Витягніть аргументи з promisResolution.

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()щоб отримати p1та p2обіцянки, розгорнуті на наступному .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

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


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

}

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