Como posso `esperar` em um Rx Observable?

106

Eu gostaria de ser capaz de esperar em um observável, por exemplo

const source = Rx.Observable.create(/* ... */)
//...
await source;

Uma tentativa ingênua resulta em esperar resolvendo imediatamente e não bloqueando a execução

Editar: o pseudocódigo para meu caso de uso pretendido completo é:

if (condition) {
  await observable;
}
// a bunch of other code

Eu entendo que posso mover o outro código para outra função separada e passá-lo para o retorno de chamada de inscrição, mas espero ser capaz de evitar isso.

Bifes baratos
fonte
Você não pode mover o código restante (que deseja aguardar pela fonte) para uma .subscribe()chamada de método?
StriplingWarrior

Respostas:

132

Você tem que passar uma promessa await. Converta o próximo evento do observável em uma promessa e espere por isso.

if (condition) {
  await observable.first().toPromise();
}

Editar nota: esta resposta originalmente usava .take (1), mas foi alterada para usar .first (), o que evita o problema de a promessa nunca ser resolvida se o fluxo termina antes de um valor chegar.

Macil
fonte
3
Em vez de take (1) você poderia usar await observable.first().toPromise();?
abril
14
@apricity Se não houver valores na conclusão, first()isso resultará em rejeição e take(1)em promessa pendente.
Estus Flask
6
@apricity @AgentME Na verdade você NÃO deve usar take(1)nem first()em casos como este. Como você está esperando que exatamente UM evento aconteça, você deve usar o single()que lançará uma exceção se houver mais de 1, enquanto não lançará uma exceção quando não houver nenhuma. Se houver mais de um, provavelmente há algo errado em seu código / modelo de dados etc. Se você não usar o único, acabará escolhendo arbitrariamente o primeiro item que retorna sem avisar que há mais. Você teria que tomar cuidado com seu predicado na fonte de dados upstream para manter sempre a mesma ordem.
ntziolis
3
Não se esqueça da importação:import 'rxjs/add/operator/first';
Stephanie
7
Agora que toPromise () está obsoleto, como devemos fazer isso?
Jus10
26

Provavelmente tem que ser

await observable.first().toPromise();

Como foi observado nos comentários anteriores, há uma diferença substancial entre os operadores take(1)e first()quando há um vazio preenchido observável.

Observable.empty().first().toPromise()resultará em rejeição com EmptyErrorisso pode ser tratado em conformidade, porque realmente não havia valor.

E Observable.empty().take(1).toPromise()resultará em resolução com undefinedvalor.

Estus Flask
fonte
Na verdade take(1), não renderá uma promessa pendente. Isso renderá uma promessa resolvida com undefined.
Johan t Hart
Obrigado por notar, isso é correto. Não sei por que o post foi diferente, possivelmente o comportamento mudou em algum ponto.
Estus Flask
8

Você vai precisar de awaituma promessa, então você vai querer usar toPromise(). Veja isto para mais detalhes em toPromise().

Josh Durham
fonte
4

Se toPromiseestiver obsoleto para você, você pode usar, .pipe(take(1)).toPromisemas como pode ver aqui , não está obsoleto.

Então, por favor, use toPromise(RxJs 6) como disse:

//return basic observable
const sample = val => Rx.Observable.of(val).delay(5000);
//convert basic observable to promise
const example = sample('First Example')
  .toPromise()
  //output: 'First Example'
  .then(result => {
    console.log('From Promise:', result);
  });

exemplo async / await:

//return basic observable
const sample = val => Rx.Observable.of(val).delay(5000);
//convert basic observable to promise
const example = await sample('First Example').toPromise()
// output: 'First Example'
console.log('From Promise:', result);

Leia mais aqui .

E remova esta afirmação errada que diz que toPromiseestá obsoleta.

Emerica
fonte