Como acesso os resultados da promessa anterior em uma cadeia .then ()?

650

Reestruturei meu código para promessas e construí uma cadeia longa e maravilhosa de promessas , consistindo em vários .then()retornos de chamada. No final, desejo retornar algum valor composto e precisar acessar vários resultados de promessa intermediária . No entanto, os valores de resolução do meio da sequência não estão no escopo no último retorno de chamada, como os acesso?

function getExample() {
    return promiseA(…).then(function(resultA) {
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        return // How do I gain access to resultA here?
    });
}
Bergi
fonte
2
Esta pergunta é realmente interessante e, mesmo se marcada javascript, é relevante em outro idioma. Eu só uso a resposta "quebrar a cadeia" em java e jdeferred
Gontard

Respostas:

377

Quebrar a corrente

Quando precisar acessar os valores intermediários em sua cadeia, você deve separá-la nas partes únicas necessárias. Em vez de anexar um retorno de chamada e de alguma forma tentar usar seu parâmetro várias vezes, anexe vários retornos à mesma promessa - sempre que precisar do valor do resultado. Não se esqueça, uma promessa representa (proxies) um valor futuro ! Depois de derivar uma promessa da outra em uma cadeia linear, use os combinadores de promessa que são fornecidos pela sua biblioteca para criar o valor do resultado.

Isso resultará em um fluxo de controle muito simples, composição clara de funcionalidades e, portanto, fácil modularização.

function getExample() {
    var a = promiseA(…);
    var b = a.then(function(resultA) {
        // some processing
        return promiseB(…);
    });
    return Promise.all([a, b]).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Em vez da desestruturação parâmetro na chamada de retorno após Promise.allque só se tornou disponível com ES6, em ES5 a thenchamada seria substituído por um método auxiliar bacana que foi fornecida por muitas bibliotecas promessa ( Q , Bluebird , quando , ...): .spread(function(resultA, resultB) { ….

O Bluebird também possui uma joinfunção dedicada para substituir essa combinação Promise.all+ spreadpor uma construção mais simples (e mais eficiente):


return Promise.join(a, b, function(resultA, resultB) {  });
Bergi
fonte
1
As funções dentro da matriz são executadas em ordem?
scaryguy
6
@ scaryguy: Não há funções na matriz, essas são promessas. promiseAe promiseBsão as funções (retorno de promessa) aqui.
Bergi
2
@Roland Nunca disse que era :-) Essa resposta foi escrita na era do ES5, em que não havia nenhuma promessa no padrão e spreadera super útil nesse padrão. Para soluções mais modernas, consulte a resposta aceita. No entanto, eu já atualizei a resposta de passagem explícita e não há realmente nenhuma boa razão para não atualizá-la também.
Bergi 30/08/19
1
@reify Não, você não deve fazer isso , isso traria problemas com rejeições.
Bergi
1
Eu não entendo esse exemplo. Se houver uma cadeia de instruções 'then' que exijam a propagação de valores por toda a cadeia, não vejo como isso resolve o problema. Uma promessa que requer um valor anterior NÃO PODE ser acionada (criada) até que esse valor esteja presente. Além disso, Promise.all () simplesmente aguarda o término de todas as promessas em sua lista: não impõe um pedido. Então, eu preciso que cada função 'next' tenha acesso a todos os valores anteriores e não vejo como o seu exemplo faz isso. Você deve nos guiar pelo seu exemplo, porque eu não acredito ou entendo.
David Spector
238

Harmonia do ECMAScript

Obviamente, esse problema também foi reconhecido pelos designers de idiomas. Eles fizeram muito trabalho e a proposta de funções assíncronas finalmente entrou em

ECMAScript 8

Você não precisa mais de uma única thenchamada ou função de retorno de chamada, pois em uma função assíncrona (que retorna uma promessa ao ser chamada), basta aguardar a resolução direta das promessas. Ele também possui estruturas de controle arbitrárias, como condições, loops e cláusulas try-catch, mas por conveniência, não precisamos delas aqui:

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

ECMAScript 6

Enquanto esperávamos o ES8, já usamos um tipo de sintaxe muito semelhante. O ES6 veio com funções geradoras , que permitem dividir a execução em partes com yieldpalavras-chave colocadas arbitrariamente . Essas fatias podem ser executadas uma após a outra, independentemente, de forma assíncrona - e é exatamente isso que fazemos quando queremos esperar por uma resolução de promessa antes de executar a próxima etapa.

Existem bibliotecas dedicadas (como co ou task.js ), mas também muitas bibliotecas promissoras têm funções auxiliares ( Q , Bluebird , quando , ...) que executam essa execução passo-a-passo assíncrona quando você fornece a elas uma função geradora que produz promessas.

var getExample = Promise.coroutine(function* () {
//               ^^^^^^^^^^^^^^^^^ Bluebird syntax
    var resultA = yield promiseA(…);
    // some processing
    var resultB = yield promiseB(…);
    // more processing
    return // something using both resultA and resultB
});

Isso funcionou no Node.js. desde a versão 4.0, também alguns navegadores (ou suas edições dev) suportaram a sintaxe do gerador relativamente cedo.

ECMAScript 5

No entanto, se você quiser / precisar ser compatível com versões anteriores, não poderá usá-las sem um transpiler. As funções do gerador e as funções assíncronas são suportadas pelas ferramentas atuais, consulte, por exemplo, a documentação do Babel nos geradores e funções assíncronas .

Além disso, também existem muitas outras linguagens de compilação em JS dedicadas a facilitar a programação assíncrona. Eles geralmente usam uma sintaxe semelhante a await(por exemplo, Iced CoffeeScript ), mas também existem outras que apresentam uma donotação semelhante a Haskell (por exemplo , LatteJs , monadic , PureScript ou LispyScript ).

Bergi
fonte
@ Bergi, você precisa aguardar a função assíncrona examinar getExample () do código externo?
Arisalexis 28/08/2015
@arisalexis: Sim, getExampleainda é uma função que retorna uma promessa, funcionando exatamente como as funções das outras respostas, mas com uma sintaxe melhor. Você pode fazer awaituma chamada em outra asyncfunção ou pode encadear .then()seu resultado.
Bergi 28/08/2015
1
Estou curioso, por que você respondeu sua própria pergunta imediatamente depois de fazer? Há uma boa discussão aqui, mas estou curioso. Talvez você tenha encontrado suas respostas sozinho depois de perguntar?
granmoe
@granmoe: eu postei toda a discussão de propósito como um alvo duplicado canônica
Bergi
Existe uma maneira (não muito trabalhosa) de evitar o uso de Promise.coroutine (ou seja, não usando Bluebird ou outra biblioteca, mas apenas JS simples) no exemplo do ECMAScript 6 com a função gerador? Eu tinha em mente algo assim, steps.next().value.then(steps.next)...mas isso não funcionou.
um aluno não tem nome
102

Inspeção síncrona

Atribuindo valores de promessa para mais tarde necessários a variáveis ​​e, em seguida, obtendo seu valor por meio de inspeção síncrona. O exemplo usa o .value()método bluebird, mas muitas bibliotecas fornecem um método semelhante.

function getExample() {
    var a = promiseA(…);

    return a.then(function() {
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // a is guaranteed to be fulfilled here so we can just retrieve its
        // value synchronously
        var aValue = a.value();
    });
}

Isso pode ser usado para quantos valores você desejar:

function getExample() {
    var a = promiseA(…);

    var b = a.then(function() {
        return promiseB(…)
    });

    var c = b.then(function() {
        return promiseC(…);
    });

    var d = c.then(function() {
        return promiseD(…);
    });

    return d.then(function() {
        return a.value() + b.value() + c.value() + d.value();
    });
}
Esailija
fonte
6
Esta é a minha resposta favorita: legível, extensível e mínima dependência de biblioteca ou linguagem características
Jason
13
@ Jason: Uh, " dependência mínima dos recursos da biblioteca "? A inspeção síncrona é um recurso de biblioteca e não é padrão para inicializar.
Bergi
2
Eu acho que ele quis dizer características específicas da biblioteca
DEATHGAZE
54

Aninhamento (e) fechamentos

Usar closures para manter o escopo de variáveis ​​(no nosso caso, os parâmetros da função de retorno de chamada de sucesso) é a solução JavaScript natural. Com promessas, podemos arbitrariamente aninhar e nivelar .then() retornos de chamada - eles são semanticamente equivalentes, exceto pelo escopo do interno.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(function(resultB) {
            // more processing
            return // something using both resultA and resultB;
        });
    });
}

Claro, isso está construindo uma pirâmide de indentação. Se a indentação estiver ficando muito grande, você ainda poderá aplicar as ferramentas antigas para combater a pirâmide da desgraça : modularize, use funções nomeadas extras e achatar a cadeia de promessas assim que não precisar mais de uma variável.
Em teoria, você sempre pode evitar mais de dois níveis de aninhamento (explicitando todos os fechamentos); na prática, use quantos forem razoáveis.

function getExample() {
    // preprocessing
    return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
    return function(resultA) {
        // some processing
        return promiseB(…).then(makeBhandler(resultA, …));
    };
}
function makeBhandler(resultA, …) {
    return function(resultB) {
        // more processing
        return // anything that uses the variables in scope
    };
}

Você também pode usar funções auxiliares para este tipo de aplicação parcial , como _.partiala partir Sublinhado / lodash ou o nativo .bind()método , a redução ainda maior recuo:

function getExample() {
    // preprocessing
    return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
    // some processing
    return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
    // more processing
    return // anything that uses resultA and resultB
}
Bergi
fonte
5
Essa mesma sugestão é dada como solução para o 'Erro avançado nº 4' no artigo de Nolan Lawson sobre promessas pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html . É uma boa leitura.
Robert
2
Esta é exatamente a bindfunção em Mônadas. Haskell fornece açúcar sintático (do-notation) para fazer com que pareça com sintaxe assíncrona / aguardada.
Zeronone 6/08
50

Passagem explícita

Semelhante ao aninhamento dos retornos de chamada, essa técnica depende de fechamentos. No entanto, a cadeia permanece plana - em vez de passar apenas o último resultado, algum objeto de estado é passado para cada etapa. Esses objetos de estado acumulam os resultados das ações anteriores, entregando todos os valores que serão necessários mais tarde, mais o resultado da tarefa atual.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Aqui, essa pequena flecha b => [resultA, b]é a função que se fecha resultAe passa uma matriz de ambos os resultados para a próxima etapa. Que usa a sintaxe de destruição de parâmetros para dividi-lo em variáveis ​​únicas novamente.

Antes que a desestruturação se tornasse disponível no ES6, um método bacana chamado .spread()era fornecido por muitas bibliotecas de promessas ( Q , Bluebird , quando ,…). É preciso usar uma função com vários parâmetros - um para cada elemento da matriz - como .spread(function(resultA, resultB) { ….

Obviamente, esse fechamento necessário aqui pode ser ainda mais simplificado por algumas funções auxiliares, por exemplo

function addTo(x) {
    // imagine complex `arguments` fiddling or anything that helps usability
    // but you get the idea with this simple one:
    return res => [x, res];
}


return promiseB(…).then(addTo(resultA));

Como alternativa, você pode empregar Promise.allpara produzir a promessa para a matriz:

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
                                                    // as if passed to Promise.resolve()
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

E você pode não apenas usar matrizes, mas objetos arbitrariamente complexos. Por exemplo, com _.extendou Object.assignem uma função auxiliar diferente:

function augment(obj, name) {
    return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(augment({resultA}, "resultB"));
    }).then(function(obj) {
        // more processing
        return // something using both obj.resultA and obj.resultB
    });
}

Embora esse padrão garanta uma cadeia plana e objetos de estado explícito possam melhorar a clareza, ele se tornará tedioso para uma cadeia longa. Especialmente quando você precisa apenas do estado esporadicamente, ainda precisa passar por cada passo. Com essa interface fixa, os retornos de chamada únicos na cadeia são bastante acoplados e inflexíveis para alterações. Isso dificulta a realização de etapas únicas e os retornos de chamada não podem ser fornecidos diretamente de outros módulos - eles sempre precisam ser agrupados em código padrão que se preocupa com o estado. Funções auxiliares abstratas, como as descritas acima, podem aliviar um pouco a dor, mas sempre estará presente.

Bergi
fonte
Primeiro, não acho que a sintaxe que omita Promise.alldeve ser incentivada (ela não funcionará no ES6 quando a desestruturação a substituir e a troca de um .spreadpara a thenfornece resultados inesperados às pessoas.) A partir do aumento - não sei por que você precisa usar o aumento - adicionar itens ao protótipo de promessa não é uma maneira aceitável de estender as promessas do ES6 de qualquer maneira, que deveriam ser estendidas com a subclasse (atualmente não suportada).
Benjamin Gruenbaum
@BenjaminGruenbaum: O que você quer dizer com " omissão de sintaxePromise.all "? Nenhum dos métodos nesta resposta será interrompido com o ES6. Mudar de spreadpara uma desestruturação thentambém não deve ter problemas. Re .prototype.augment: Eu sabia que alguém iria notar, eu só gostava de explorar possibilidades - editá-lo.
Bergi 31/01
Pela sintaxe da matriz, quero dizer, em return [x,y]; }).spread(...vez de return Promise.all([x, y]); }).spread(...que não mudaria ao trocar o spread pelo açúcar de desestruturação es6 e também não seria um caso estranho, onde as promessas tratam as matrizes retornadas de maneira diferente de tudo o resto.
Benjamin Gruenbaum 31/01
3
Esta é provavelmente a melhor resposta. As promessas são "Programação Reativa Funcional", e essa costuma ser a solução empregada. Por exemplo, BaconJs, tem #combineTemplate que lhe permite combinar os resultados em um objeto que é passado para baixo da cadeia
U Avalos
1
@ CapiEtheriel A resposta foi escrita quando o ES6 não era tão amplo quanto é hoje. Sim, talvez seja a hora de trocar os exemplos
Bergi
35

Estado contextual mutável

A solução trivial (mas deselegante e bastante propensa a erros) é usar apenas variáveis ​​de escopo mais alto (às quais todos os retornos de chamada na cadeia têm acesso) e gravar valores de resultado para eles quando você os obtiver:

function getExample() {
    var resultA;
    return promiseA(…).then(function(_resultA) {
        resultA = _resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both resultA and resultB
    });
}

Em vez de muitas variáveis, pode-se também usar um objeto (inicialmente vazio), no qual os resultados são armazenados como propriedades criadas dinamicamente.

Esta solução tem várias desvantagens:

  • O estado mutável é feio e as variáveis ​​globais são más .
  • Esse padrão não funciona além dos limites das funções; modularizar as funções é mais difícil, pois suas declarações não devem deixar o escopo compartilhado
  • O escopo das variáveis ​​não impede o acesso a elas antes de serem inicializadas. Isso é especialmente provável para construções complexas de promessas (loops, ramificações, exceções) onde as condições da corrida podem ocorrer. A passagem explícita do estado, um design declarativo que promete encorajar, força um estilo de codificação mais limpo que pode impedir isso.
  • É preciso escolher o escopo para essas variáveis ​​compartilhadas corretamente. Ele precisa ser local para a função executada para evitar condições de corrida entre várias invocações paralelas, como seria o caso se, por exemplo, o estado fosse armazenado em uma instância.

A biblioteca Bluebird incentiva o uso de um objeto que é passado adiante, usando seu bind()método para atribuir um objeto de contexto a uma cadeia de promessas. Ele estará acessível a partir de cada função de retorno de chamada por meio da thispalavra - chave inutilizável . Enquanto as propriedades do objeto são mais propensas a erros de digitação não detectados do que variáveis, o padrão é bastante inteligente:

function getExample() {
    return promiseA(…)
    .bind({}) // Bluebird only!
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }).bind(); // don't forget to unbind the object if you don't want the
               // caller to access it
}

Essa abordagem pode ser facilmente simulada em bibliotecas de promessas que não suportam .bind (embora de uma maneira um pouco mais detalhada e não possam ser usadas em uma expressão):

function getExample() {
    var ctx = {};
    return promiseA(…)
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }.bind(ctx)).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }.bind(ctx));
}
Bergi
fonte
.bind()é desnecessário para a prevenção de vazamento de memória
Esailija
@ Esailija: Mas a promessa retornada não contém uma referência ao objeto de contexto? OK, é claro que a coleta de lixo lidará com isso mais tarde; não é um "vazamento", a menos que a promessa nunca seja descartada.
Bergi 31/01
Sim, mas as promessas também mantêm referência aos seus valores de cumprimento e razões de erro ... mas nada mantém referência à promessa, portanto não importa.
Esailija
4
Por favor, divida esta resposta em duas, já que quase votei no preâmbulo! Penso que "a solução trivial (mas deselegante e bastante propensa a erros)" é a solução mais limpa e mais simples, pois não se baseia mais em fechamentos e estados mutáveis ​​do que a sua resposta automática aceita, mas é mais simples. Os fechamentos não são globais nem maus. Os argumentos apresentados contra essa abordagem não fazem sentido para mim, dada a premissa. Que problemas de modularização podem ter uma "maravilhosa cadeia de promessas longas e planas"?
jib
2
Como eu disse acima, as promessas são "Programação reativa funcional". Este é um antipadrão no FRP
U Avalos
15

Uma rotação menos dura do "estado contextual mutável"

Usar um objeto com escopo local para coletar os resultados intermediários em uma cadeia de promessas é uma abordagem razoável para a pergunta que você fez. Considere o seguinte trecho:

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(paramsA).then(function(resultA){
        results.a = resultA;
        return promiseB(paramsB);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(paramsC);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • Variáveis ​​globais são ruins, portanto, esta solução usa uma variável com escopo local que não causa danos. Só é acessível dentro da função.
  • O estado mutável é feio, mas isso não muda o estado de maneira feia. O feio estado mutável tradicionalmente se refere à modificação do estado dos argumentos da função ou variáveis ​​globais, mas essa abordagem simplesmente modifica o estado de uma variável com escopo local que existe com o único objetivo de agregar resultados prometidos ... uma variável que morrerá como uma morte simples uma vez que a promessa seja resolvida.
  • Promessas intermediárias não são impedidas de acessar o estado do objeto de resultados, mas isso não introduz um cenário assustador, em que uma das promessas da cadeia ficará desonesta e sabotar seus resultados. A responsabilidade de definir os valores em cada etapa da promessa está confinada a essa função e o resultado geral será correto ou incorreto ... não haverá erros que surgirão anos mais tarde na produção (a menos que você pretenda !)
  • Isso não introduz um cenário de condição de corrida que surgiria da chamada paralela porque uma nova instância da variável results é criada para cada chamada da função getExample.
Jay
fonte
1
Pelo menos evite o Promiseconstrutor antipadrão !
Bergi 25/03
Obrigado @Bergi, eu nem percebi que isso era um anti-padrão até você mencionar!
27417 Jay
esta é uma boa solução alternativa para atenuar o erro relacionado à promessa. Eu estava usando o ES5 e não queria adicionar outra biblioteca para trabalhar com a promessa.
Nilakantha singh deo 25/05
8

O nó 7.4 agora suporta chamadas assíncronas / em espera com o sinalizador de harmonia.

Tente o seguinte:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

e execute o arquivo com:

node --harmony-async-await getExample.js

Simples como pode ser!

Anthony
fonte
8

Hoje em dia, eu também tenho algumas perguntas como você. Por fim, encontro uma boa solução com a pergunta, é simples e bom de ler. Espero que isso possa ajudá-lo.

De acordo com o how-to-chain-javascript-promises

ok, vamos olhar o código:

const firstPromise = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('first promise is completed');
            resolve({data: '123'});
        }, 2000);
    });
};

const secondPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('second promise is completed');
            resolve({newData: `${someStuff.data} some more data`});
        }, 2000);
    });
};

const thirdPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('third promise is completed');
            resolve({result: someStuff});
        }, 2000);
    });
};

firstPromise()
    .then(secondPromise)
    .then(thirdPromise)
    .then(data => {
        console.log(data);
    });
yzfdjzwl
fonte
4
Isso realmente não responde à pergunta sobre como acessar os resultados anteriores na cadeia.
Bergi
2
Toda promessa pode obter o valor anterior, qual é o seu significado?
yzfdjzwl
1
Dê uma olhada no código da pergunta. O objetivo não é obter o resultado da promessa .thenexigida, mas antes disso. Por exemplo, thirdPromiseacessando o resultado de firstPromise.
Bergi 26/07/19
6

Outra resposta, usando a babel-nodeversão <6

Usando async - await

npm install -g [email protected]

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Então corra babel-node example.jse pronto!

Anthony
fonte
1
Sim, fiz, logo após publicar o meu. Ainda assim, vou deixar porque explica como realmente começar a usar o ES7, em vez de apenas dizer que um dia o ES7 estará disponível.
Anthony
1
Bem, eu devo atualizar minha resposta para dizer que os plugins "experimentais" para esses já estão aqui.
Bergi 22/11/2015
2

Não vou usar esse padrão no meu próprio código, pois não sou muito fã de usar variáveis ​​globais. No entanto, em uma pitada, ele funcionará.

O usuário é um modelo de Mongoose promissificado.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});
Anthony
fonte
2
Observe que esse padrão já está detalhado no estado contextual mutável resposta (e também porque é feia - eu não sou um grande fã quer)
Bergi
No seu caso, o padrão parece ser inútil. Você não precisa de globalVarnada, apenas precisa User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Bergi 11/08/2015
1
Eu não preciso disso pessoalmente no meu próprio código, mas o usuário pode precisar executar mais assíncrono na segunda função e depois interagir com a chamada Promise original. Mas, como mencionado, vou usar geradores nesse caso. :)
Anthony
2

Outra resposta, usando o executor seqüencial nsynjs :

function getExample(){

  var response1 = returnPromise1().data;

  // promise1 is resolved at this point, '.data' has the result from resolve(result)

  var response2 = returnPromise2().data;

  // promise2 is resolved at this point, '.data' has the result from resolve(result)

  console.log(response, response2);

}

nynjs.run(getExample,{},function(){
    console.log('all done');
})

Atualização: exemplo de trabalho adicionado

function synchronousCode() {
     var urls=[
         "https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
     ];
     for(var i=0; i<urls.length; i++) {
         var len=window.fetch(urls[i]).data.text().data.length;
         //             ^                   ^
         //             |                   +- 2-nd promise result
         //             |                      assigned to 'data'
         //             |
         //             +-- 1-st promise result assigned to 'data'
         //
         console.log('URL #'+i+' : '+urls[i]+", length: "+len);
     }
}

nsynjs.run(synchronousCode,{},function(){
    console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

amaksr
fonte
1

Ao usar o bluebird, você pode usar o .bindmétodo para compartilhar variáveis ​​na cadeia de promessa:

somethingAsync().bind({})
.spread(function (aValue, bValue) {
    this.aValue = aValue;
    this.bValue = bValue;
    return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
    return this.aValue + this.bValue + cValue;
});

verifique este link para obter mais informações:

http://bluebirdjs.com/docs/api/promise.bind.html

alphakevin
fonte
Observe que esse padrão já está detalhado no estado contextual mutável resposta
Bergi
1
function getExample() {
    var retA, retB;
    return promiseA(…).then(function(resultA) {
        retA = resultA;
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        //retA is value of promiseA
        return // How do I gain access to resultA here?
    });
}

maneira fácil: D

Minh Giang
fonte
Você notou esta resposta ?
21317 Bergi (
1

Eu acho que você pode usar o hash do RSVP.

Algo como o abaixo:

    const mainPromise = () => {
        const promise1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('first promise is completed');
                resolve({data: '123'});
            }, 2000);
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('second promise is completed');
                resolve({data: '456'});
            }, 2000);
        });

        return new RSVP.hash({
              prom1: promise1,
              prom2: promise2
          });

    };


   mainPromise()
    .then(data => {
        console.log(data.prom1);
        console.log(data.prom2);
    });
Vishu
fonte
Sim, é o mesmo que a Promise.allsolução , apenas com um objeto em vez de uma matriz.
Bergi 29/08/19
0

Solução:

Você pode colocar valores intermediários no escopo em qualquer função posterior 'then' explicitamente, usando 'bind'. É uma solução agradável que não requer alteração no funcionamento do Promises e requer apenas uma ou duas linhas de código para propagar os valores, assim como os erros já são propagados.

Aqui está um exemplo completo:

// Get info asynchronously from a server
function pGetServerInfo()
    {
    // then value: "server info"
    } // pGetServerInfo

// Write into a file asynchronously
function pWriteFile(path,string)
    {
    // no then value
    } // pWriteFile

// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
    {
    var scope={localInfo:localInfo}; // Create an explicit scope object
    var thenFunc=p2.bind(scope); // Create a temporary function with this scope
    return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
    } // pLogInfo

// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
    {
    // Do the final 'then' in the chain: Writes "local info, server info"
    return pWriteFile('log',this.localInfo+','+serverInfo);
    } // p2

Esta solução pode ser invocada da seguinte maneira:

pLogInfo("local info").then().catch(err);

(Nota: uma versão mais complexa e completa desta solução foi testada, mas não esta versão de exemplo, portanto, pode haver um erro.)

David Spector
fonte
Este parece ser o mesmo padrão como no assentamento (e) Os encerramentos resposta
Bergi
Parece semelhante. Desde então, aprendi que a nova sintaxe Async / Await inclui ligação automática de argumentos; portanto, todos os argumentos estão disponíveis para todas as funções assíncronas. Estou abandonando promessas.
David Spector
async/ awaitainda significa usar promessas. O que você pode abandonar são as thenchamadas com retornos de chamada.
Bergi 28/08/19
-1

O que eu aprendo sobre promessas é usá-lo apenas porque os valores de retorno evitam referenciá-los, se possível. A sintaxe assíncrona / aguardada é particularmente prática para isso. Hoje, todos os navegadores e nós mais recentes o suportam: https://caniuse.com/#feat=async-functions , é um comportamento simples e o código é como ler código síncrono, esquecer retornos de chamada ...

Nos casos em que preciso fazer referência a promessas, é quando a criação e a resolução ocorrem em locais independentes / não relacionados. Portanto, em vez de uma associação artificial e provavelmente um ouvinte de eventos apenas para resolver a promessa "distante", prefiro expor a promessa como adiada, que o código a seguir implementa em es5 válido

/**
 * Promise like object that allows to resolve it promise from outside code. Example:
 *
```
class Api {
  fooReady = new Deferred<Data>()
  private knower() {
    inOtherMoment(data=>{
      this.fooReady.resolve(data)
    })
  }
}
```
 */
var Deferred = /** @class */ (function () {
  function Deferred(callback) {
    var instance = this;
    this.resolve = null;
    this.reject = null;
    this.status = 'pending';
    this.promise = new Promise(function (resolve, reject) {
      instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
      instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
    });
    if (typeof callback === 'function') {
      callback.call(this, this.resolve, this.reject);
    }
  }
  Deferred.prototype.then = function (resolve) {
    return this.promise.then(resolve);
  };
  Deferred.prototype.catch = function (r) {
    return this.promise.catch(r);
  };
  return Deferred;
}());

transpilados de um projeto datilografado meu:

https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31

Para casos mais complexos, costumo usar esses pequenos utilitários promissores, sem dependências testadas e digitadas. O p-map foi útil várias vezes. Acho que ele cobriu a maioria dos casos de uso:

https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=

cancerbero
fonte
Parece que você está sugerindo estado contextual mutável ou inspeção síncrona ?
Bergi 12/09/19
@bergi Primeira vez em que encabeço esses nomes. adicionando à lista obrigado. Conheço esse tipo de promessas autoconscientes com o nome de adiado - BTW, a implementação é apenas uma promessa com a resolução finalizada. Muitas vezes preciso desse padrão nos casos em que a responsabilidade de criação e resolução de promessas é independente, portanto não há necessidade de relacioná-los apenas para resolver uma promessa. Eu adaptei, mas não para o seu exemplo, e usando uma classe, mas talvez equivalente.
Cancerbero