2010-02-08 3 views
7

Voglio trasformare una funzione asincrona in sincrono.Programmazione asincrona in JavaScript senza callback disordinati

function fetch() { 
    var result = 'snap!'; 
    $.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?", function messyCallback(data){ 
    result = data; 
    }); 
    return result; 
} 

document.write(fetch());​

See in action

Il risultato sarà sempre 'Snap!', Perché $.getJSON fuga dopo fetch() è fatto.

La mia prima idea era:

function fetch() { 
    var result = 'snap!'; 
    $.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?", function messyCallback(data){ 
    result = data; 
    }); 
    while (true) { if (result != 'snap!') return result; } 
}

Non funziona e anche soffiare via il browser.

Ho letto su generators and iterators in JS 1.7, ma non ho idea di come applicarlo al mio problema.

Questa domanda non riguarda davvero jQuery. Invece di $.getJSON potrebbe essere una qualsiasi altra funzione asincrona.

+0

possibile (probabile?) Duplicati - vedi http://stackoverflow.com/questions/1455870/jquery-wait-for-function-to-complete -to-continua-elaborazione. –

+0

Jeff, non penso. Io uso la chiamata JSONP, che è sempre sincrona. – NVI

+1

@NV: JSONP è solo sincrono se analizzato come parte del documento HTML. Se viene aggiunto alla pagina usando il DOM, è asincrono. –

risposta

9

Vai a questa domanda anche: Halt JavaScript execution without locking up the browser

facendo esattamente ciò che si vuole non funziona. Non è possibile creare la sincronia dall'asincrona (solo viceversa!) In un ambiente basato su eventi a thread singolo. Dovresti abbracciare la natura asincrona del linguaggio e sviluppare il tuo stile coerente attorno alla gestione dei callback in modo che il tuo codice sia leggibile/manutenibile. Potresti, ad esempio, scegliere di non fare mai alcun lavoro reale all'interno di una chiusura di richiamata, ma semplicemente chiamare un'altra funzione/metodo di primo livello per maggiore chiarezza.

1

Il livello inferiore $.ajax() function da jQuery ha più opzioni, tra cui async: [true|false] (l'impostazione predefinita è true).

Tuttavia, nella maggior parte dei casi è necessario seguire il consiglio di Ben e "abbracciare la natura asincrona della lingua".

+0

AFAIK, queste opzioni non funzionano con le chiamate JSONP. – NVI

+0

Drew: Grazie per averlo chiamato. È vero che JS ha un piccolo numero di chiamate sincrone (in modo confuso), essendo 'get' di XMLHttpRequest uno di questi. –

+0

Intendevo che 'async: false' non funziona con le chiamate JSONP. – NVI

2

Si desidera rollare il proprio $ .getSyncJSON che utilizza $ .ajax ({async: false}). Vedi: http://api.jquery.com/jQuery.ajax/.

Devo tuttavia consigliarti contro questa linea di condotta. Puoi facilmente bloccare il browser da tutti gli input, mettendo la tua interfaccia utente alla mercé della rete. Ma se sai cosa stai facendo e sei sicuro del tuo caso d'uso, fallo.

2

Invece di scrivere metodi helper come il vostro fetch che restituiscono un valore, far loro accettare un'altra funzione, un "ricevitore", per passare la loro risultato:

function fetch(receiver) { 

    $.getJSON("blah...", function(data) { 

     receiver(data); 
    }); 
} 

Ovviamente questo è ridondante perché è esattamente come getJSON funziona già, ma in un esempio più realistico la funzione fetch elaborerebbe o filtrerebbe il risultato in qualche modo prima di trasmetterlo.

Allora, invece di:

document.write(fetch());​ 

Faresti:

fetch(function(result) { document.write(result); }); 

generatori possono essere utilizzati per rendere il codice asincrono molto più lineare in stile. Ogni volta che avevi bisogno di un risultato asincrono, avresti dato una funzione per avviarlo, e il generatore sarebbe ripreso quando il risultato era disponibile.Ci sarebbe un po 'di codice di gestione per far funzionare il generatore. Ma questo non è di grande aiuto perché i generatori non sono standard tra i vari browser.

Se sei interessato, ecco uno blog post about using generators to tidy up asynchronous code.

+0

Nel codice il ricevitore deve trovarsi all'interno della funzione di callback. – NVI

+0

@NV - oops, buona chiamata. –

+0

Ho avuto un gioco con i generatori Mozilla e sembrano eccellenti. Peccato che non siano ampiamente adottati altrove. Vedi il link che ho aggiunto alla fine della risposta. –

2

C'è un'estensione del linguaggio JavaScript chiamato StratifiedJS. Funziona in ogni browser e ti consente di fare proprio questo: gestire i problemi asincroni in modo sincrono/lineare senza bloccare il browser.

È possibile abilitare JavaScript stratificato, ad es. includendo Oni Apollo nella vostra pagina web come:

<script src="http://code.onilabs.com/latest/oni-apollo.js"></script> 
<script type="text/sjs"> your StratifiedJS code here </script> 

E il codice sarebbe simile:

function fetch() { 
    return require("http").jsonp(
    "http://api.flickr.com/services/feeds/photos_public.gne?" + 
    "tags=cat&tagmode=any&format=json", {cbfield:"jsoncallback"}); 
} 
document.write(fetch());​ 

Oppure, se si vuole veramente utilizzare jQuery in StratifiedJS:

require("jquery-binding").install(); 
function fetch() { 
    var url = "http://api.flickr.com/?format=json&...&jsoncallback=?" 
    return $.$getJSON(url); 
} 
document.write(fetch());​ 

La documentazione sono il http://onilabs.com/docs

2

La libreria TameJS è progettata per questo problema

Si potrebbe scrivere qualcosa di simile (non testata):

var result = 'snap!'; 
await { 
    $.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?", defer(result)); 
} 
return result;