2015-03-07 17 views
24

Sto facendo qualche test unitario. Il framework di test carica una pagina in un iFrame e quindi esegue asserzioni su quella pagina. Prima di iniziare ogni test, creo un Promise che imposta l'evento onload dell'iFrame per chiamare , imposta src dell'Iframe e restituisce la promessa.Come attendere la risoluzione di una promessa JavaScript prima di riprendere la funzione?

Quindi, posso semplicemente chiamare loadUrl(url).then(myFunc) e attenderà che la pagina venga caricata prima di eseguire qualsiasi cosa sia myFunc.

Uso questo tipo di modello in tutti i punti dei test (non solo per il caricamento degli URL), principalmente per consentire modifiche al DOM (ad esempio, fare clic su un pulsante e attendere che div si nascondano e mostrare).

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 viene completata prima dell'esecuzione della promessa.

Mi chiedo se esiste un modo per ottenere un valore da Promise o attendere (blocco/sospensione) finché non viene risolto, in modo simile a .NET IAsyncResult.WaitHandle.WaitOne(). 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);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 
<div id="output"></div>

+0

se si inseriscono le chiamate in append in una funzione riutilizzabile, è quindi possibile() come necessario per ASCIUGARE. puoi anche creare gestori multi-purpose, guidati da _questo_ per alimentare le chiamate then(), come '.then (fnAppend.bind (myDiv))', che può ridurre enormemente gli anon. – dandavis

+0

Con cosa stai provando? Se è un browser moderno o puoi usare uno strumento come BabelJS per trascrivere il tuo codice, questo è certamente possibile. –

risposta

12

mi chiedo se non v'è alcun modo per ottenere un valore da una promessa o attesa (blocco/sonno) fino a quando non è risolta, simile a quella di .NET IAsyncResult .WaitHandle.WaitOne(). So che JavaScript è single-threaded, ma spero che ciò non significhi che una funzione non può produrre.

L'attuale generazione di javascript nel browser non dispone di un wait() o sleep() che permette altre cose per l'esecuzione. 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 è a causa della singola threadnessness 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 da essere in grado di utilizzarli 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.

io non sono sicuro di aver capito esattamente che ordine si sta cercando di raggiungere nel codice, ma si poteva fare qualcosa di simile utilizzando il tuo kickoff() funzione esistente e poi basta collegare un gestore .then() ad essa dopo definendolo:

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 vi darà output come questo in un ordine garantito:

start 
middle 
end 

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

Se sia transpile il codice o esegue il codice in un ambiente che supporta ES7 caratteristiche come async e await, è ora possibile utilizzare await di fare "apparire" il codice per attendere il risultato di una promessa. Si sta ancora sviluppando con promesse. Non blocca ancora tutto il Javascript, ma ti permette di scrivere operazioni sequenziali con una sintassi più amichevole.

Invece del modo ES6 di fare le cose:

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

Si può fare questo:

// 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 returns 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 
}); 
+2

Bene, usare 'then()' è quello che ho fatto. Semplicemente non mi piace dover scrivere 'function() {...}' tutto il tempo. Ingombra il codice. – dfoverdx

+3

@dfoverdx - la codifica asincrona in Javascript comporta sempre dei callback, quindi è sempre necessario definire una funzione (anonima o denominata). Al momento non c'è modo per aggirarlo. – jfriend00

+2

Sebbene sia possibile utilizzare la notazione a freccia ES6 per renderla più concisa. '(foo) => {...}' invece di 'function (foo) {...}' –

3

se si sta utilizzando ES2016 è possibile utilizzare async e await e fare qualcosa di simile:

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

Se si utilizza ES2015 è possibile utilizzare Generators. Se la sintassi non ti piace, puoi estrapolarla usando uno async utility function come explained here.

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

+0

L'esempio del generatore non funziona, manca un corridore. Si prega di non raccomandare più generatori quando è possibile solo traspolare ES8 'async' /' await'. – Bergi

+3

Grazie per il vostro feedback.Ho rimosso l'esempio del generatore e collegato a un tutorial di Generator più completo con la relativa libreria OSS. –

Problemi correlati