Come attendere la risoluzione di una promise JavaScript prima di riprendere la funzione?

Sto facendo dei test unitari. Il framework di test carica una pagina in un iFrame e quindi esegue asserzioni su quella pagina. Prima di iniziare ogni test, creo una Promise che imposta l’evento onload per chiamare resolve() , imposta lo src di iFrame e restituisce la promise.

Quindi, posso solo chiamare loadUrl(url).then(myFunc) , e attenderà che la pagina myFunc caricata prima di eseguire qualunque myFunc sia myFunc .

Io uso questo tipo di pattern dappertutto nei miei test (non solo per caricare gli URL), principalmente per consentire che vengano apportate modifiche al DOM (ad esempio, facendo clic su un pulsante, e attendere che i div si nascondano e mostrino).

Il lato negativo di questo design è che sto scrivendo costantemente funzioni anonime con poche righe di codice al loro interno. Inoltre, mentre ho un work-around (QUnit’s assert.async() ), la funzione di test che definisce le promesse termina prima che la promise venga eseguita.

Mi chiedo se c’è un modo per ottenere un valore da una Promise o aspettare (blocco / IAsyncResult.WaitHandle.WaitOne() ) finché non si è risolto, in modo simile a IAsyncResult.WaitHandle.WaitOne() di .NET. So che JavaScript è single-threaded, ma spero che ciò non significhi che una funzione non può produrre.

In sostanza, c’è un modo per ottenere quanto segue per sputare i risultati nell’ordine corretto?

 function kickOff() { return new Promise(function(resolve, reject) { $("#output").append("start"); setTimeout(function() { resolve(); }, 1000); }).then(function() { $("#output").append(" middle"); return " end"; }); }; function getResultFrom(promise) { // todo return " end"; } var promise = kickOff(); var result = getResultFrom(promise); $("#output").append(result); 
  

Mi chiedo se c’è un modo per ottenere un valore da una Promessa o aspettare (blocco / sospensione) finché non si è risolto, in modo simile a IAsyncResult.WaitHandle.WaitOne () di .NET. So che JavaScript è single-threaded, ma spero che ciò non significhi che una funzione non può produrre.

L’attuale generazione di Javascript nei browser non ha un wait() o sleep() che consente ad altre cose di funzionare. Quindi, semplicemente non puoi fare quello che stai chiedendo. Invece, ha operazioni asincrone che faranno le loro cose e poi ti chiameranno quando avranno finito (visto che hai usato le promesse per).

Parte di questo è dovuto alla singola threadedness di Javascript. Se il thread singolo sta girando, nessun altro Javascript può essere eseguito fino a quando non viene eseguito quel thread. ES6 introduce yield e generatori che permetteranno alcuni trucchi collaborativi come quello, ma siamo abbastanza diversi dall’essere in grado di usarli in un ampio campionario di browser installati (possono essere usati in alcuni sviluppi sul lato server dove controlli il JS motore che viene utilizzato).


Un’attenta gestione del codice basato su promesse può controllare l’ordine di esecuzione di un gruppo di operazioni asincrone.

Non sono sicuro di aver capito esattamente quale ordine stai cercando di ottenere nel tuo codice, ma potresti fare qualcosa di simile usando la tua funzione di kickoff() esistente e quindi semplicemente associare un gestore .then() ad esso dopo averlo chiamato:

 function kickOff() { return new Promise(function(resolve, reject) { $("#output").append("start"); setTimeout(function() { resolve(); }, 1000); }).then(function() { $("#output").append(" middle"); return " end"; }); } kickoff().then(function(result) { // use the result here $("#output").append(result); }); 

Questo ti darà un risultato come questo in un ordine garantito:

 start middle end 

Aggiornamento nel 2018 (tre anni dopo che questa risposta è stata scritta):

Se si traspone il codice o si esegue il codice in un ambiente che supporta funzionalità di ES7 come async e in await , è ora ansible utilizzare await per rendere il codice “visualizzato” in attesa del risultato di una promise. Si sta ancora sviluppando con promesse. Non blocca ancora tutto il Javascript, ma ti permette di scrivere operazioni sequenziali con una syntax più amichevole.

Invece del modo di fare ES6:

 someFunc().then(someFunc2).then(result => { // process result here }).catch(err => { // process error here }); 

Puoi farlo:

 // returns a promise async function wrapperFunc() { try { let r1 = await someFunc(); let r2 = await someFunc2(r1); // now process r2 return someValue; // this will be resolved value of the returned promise } catch(e) { console.log(e); throw e; // let caller know the promise rejected with this reason } } wrapperFunc().then(result => { // got final result }).catch(err => { // got error }); 

Se stai usando ES2016 puoi usare async e await e fare qualcosa del tipo:

 (async () => { const data = await fetch(url) myFunc(data) }()) 

Se stai usando ES2015 puoi usare i generatori . Se non ti piace la syntax puoi estrapolarla usando una funzione di utilità async come spiegato qui .

Infine, se stai usando ES5 probabilmente vorrai una libreria come Bluebird per darti un maggiore controllo.

Un’altra opzione è quella di usare Promise.all per aspettare che venga risolta una serie di promesse. Il codice qui sotto mostra come aspettare che tutte le promesse si risolvano e poi trattare i risultati una volta che sono tutti pronti (come sembrava essere l’objective della domanda); Tuttavia, l’avvio potrebbe ovviamente uscire prima che il middle si sia risolto aggiungendo quel codice prima che le chiamate vengano risolte.

 function kickOff() { let start = new Promise((resolve, reject) => resolve("start")) let middle = new Promise((resolve, reject) => { setTimeout(() => resolve(" middle"), 1000) }) let end = new Promise((resolve, reject) => resolve(" end")) Promise.all([start, middle, end]).then(results => { $("#output").append(results[0]) $("#output").append(results[1]) $("#output").append(results[2]) }) } kickOff()