Я недавно пару раз сталкивался с определенной ситуацией, которую не знал, как ее решить. Предположим следующий код:
somethingAsync()
.then( afterSomething )
.then( afterSomethingElse )
function afterSomething( amazingData ) {
return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}
Теперь может возникнуть ситуация, когда я хотел бы получить доступ к amazingData
файлу afterSomethingElse
.
Одним из очевидных решений было бы вернуть массив или хеш из afterSomething
, потому что, ну, вы можете вернуть только одно значение из функции. Но мне интересно, есть ли способ afterSomethingElse
принять 2 параметра и вызвать его аналогичным образом, поскольку это кажется намного проще документировать и понимать.
Меня только интересует эта возможность, поскольку она есть Q.spread
, что делает что-то похожее на то, что я хочу.
javascript
promise
q
Der Hochstapler
источник
источник
Ответы:
Вы не можете разрешить обещание с несколькими свойствами, так же как вы не можете вернуть несколько значений из функции . Обещание концептуально представляет собой значение с течением времени, поэтому, хотя вы можете представлять составные значения, вы не можете поместить несколько значений в обещание.
Обещание по сути разрешается с помощью одного значения - это часть того, как работает Q, как работает спецификация Promises / A + и как работает абстракция .
Самое близкое, что вы можете получить, - это использовать
Q.spread
и возвращать массивы или использовать деструктуризацию ES6, если она поддерживается или вы хотите использовать инструмент транспиляции, такой как BabelJS.Что касается передачи контекста по цепочке обещаний, пожалуйста, обратитесь к превосходному каноническому руководству Берги .
источник
.spread()
в соответствующем ответе было показано , как Bluebird: stackoverflow.com/a/22776850/1624862Promise.all([a, b, c]).then(function(x, y, z) {...})
правильно работает во всех современных движках Javascript, где x, y и z оцениваются в разрешенные значения a, b и c. Так что точнее будет сказать, что язык не позволяет вам делать это легко (или разумно) из пользовательского кода (потому что вы можете вернуть Promise непосредственно из предложения then, вы можете заключить свои значения в обещания, а затем заключить их в Promise .all (), чтобы получить желаемое поведение, хотя и запутанным образом).Promise.all
отрабатывает с массивом . ВPromise.all([a,b]).then((a, b) =>
b
естьundefined
. Вот почему вам нужно выполнить.then(([a, b]) =>
это деструктурирующее задание,вы можете передать только одно значение, но это может быть массив с несколькими значениями внутри, например:
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" })
источник
function step2([server, data]) { …
- таким образом вы также избегаете назначения неявным глобальным переменным. И вам действительно следует использоватьreturn
илиPromise.resolve
, а неnew Promise
конструктор в своих примерах.Вы можете вернуть объект, содержащий оба значения - в этом нет ничего плохого.
Другая стратегия - сохранить значение через замыкания, а не передавать его через:
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 }); }
источник
then
внутрь другогоthen
? Это не антипаттерн ?Вы можете сделать две вещи: вернуть объект
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 ); }
Вы также заметите, что я добавил оператор 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 () для всякого рода отличной работы.
источник
Просто создайте объект и извлеките аргументы из этого объекта.
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); });
источник
Все, что вы вернете из обещания, будет заключено в обещание, которое будет развернуто на следующем
.then()
этапе.Это становится интересным, когда вам нужно вернуть одно или несколько обещаний вместе с одним или несколькими синхронными значениями, такими как;
Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4]) .then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);
В этих случаях было бы важно использовать
Promise.all()
getp1
иp2
promises на следующем.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 */);
источник
Вы можете проверить Observable, представленный Rxjs , позволяет вам возвращать более одного значения.
источник
Просто верните кортеж:
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]));
}
источник