Pattern di programmazione funzionale per codice JavaScript asincrono

5

Ho adottato uno schema in uno dei miei progetti che mi piace molto e sospetto che sia probabile che sia qualcosa di standard. Mi piacerebbe descriverlo qui e vedere se voi ragazzi potete dirmi come altri programmatori JavaScript risolvono questo tipo di problema.

Alcune regole che ho stabilito per me stesso prima di iniziare il progetto. 1. Valutazione funzionale / pigra laddove possibile. Non fare un sacco di cose all'avvio. 2. Usa le promesse (mi capita di usare Angular's $ q, ma la sintassi è molto simile)

Per semplicità, diciamo che sto volendo aggiornare lo schermo e visualizzare dire in alto il nome dell'utente, quanti Mi Piace hanno, e il loro attuale Conteggio amici. Alcuni di questi possono sembrare artificiosi, ma nudi con me.

Nel mio controller sul lato client farei delle semplici chiamate a un servizio lato client che faccia qualcosa del tipo:

   function getCurrentUserName() {
       getCurrentUser().then( function(user) {
           return user.name;
       }
   }

   function getCurrentUserLikeCount() {
       getCurrentUser().then( function(user) {
           return user.likeCount;
       }
   }

   function getCurrentUserFriendCount() {
       getCurrentUser().then( function(user) {
           return user.friendCount;
       }
   }

Ora potrei avere più elementi dell'interfaccia utente che accedono al controller e chissà in che ordine potrebbero arrivare le richieste. Diamine, è possibile che ci possa essere un widget di terze parti che accede al controller, quindi potrebbero esserci più chiamate a una di queste routine in arrivo rapidamente.

Ora, il primo problema ovvio è che getCurrentUser () è asincrono e fa una specie di chiamata ajax / resto al server. Così: 1) Non è necessario effettuare chiamate inutili al server. Se getCurrentUser () è già stato chiamato, non è necessario chiamarlo di nuovo (dato un po 'di logica come l'utente è ancora loggato, ecc.)

Quindi supponiamo che getCurrentUser () sia abbastanza intelligente da memorizzare nella cache i risultati. Nelle chiamate successive, potrebbe restituire il valore memorizzato nella cache (sempre in modo asincrono usando when ()).

Il secondo problema è, cosa succede quando getCurrentUser () viene chiamato mentre un'altra chiamata a getCurrentUser () è già in elaborazione. La cache non è stata ancora aggiornata, quindi la seconda chiamata non vede la cache, quindi fa un'altra chiamata HTTP al server. Tu non vuoi quello. Quindi devi sapere che c'è una richiesta in sospeso al server e limitare la chiamata.

Quindi lo schema che ho adottato è che una funzione asincrona che effettua una chiamata al server segue la seguente idea di alto livello: 1. Se è la prima chiamata, invia la chiamata al server e restituisci immediatamente una promessa al cliente. Inoltre, aggrappati a questa promessa. Quando il server risponde, risolvi la promessa ma mantieni anche il valore originale restituito dal server (cioè la cache)

  1. Se non è la prima chiamata, determina se la prima chiamata è stata completata. Se il primo chiamato è già stato completato, restituire il valore restituito dal server (ad esempio la cache). Certo, lo faresti usando when () dal momento che il client si aspetta una promessa.

  2. Se non è la prima chiamata, ma la prima chiamata non è ancora stata completata, quindi restituire al client la promessa memorizzata nella cache che è stata restituita dalla prima chiamata. Quando poi la promessa viene risolta, tutti i client vengono avvisati.

In effetti, ciò che funziona anche con l'angolare $ q (e probabilmente altri), è quello di riutilizzare la promessa originale anche dopo che è stata restituita in passato. Quindi puoi evitare di memorizzare i dati nella cache poiché la promessa contiene i dati restituiti dal server.

Io chiamo tutto ciò che strozza, per mancanza di un termine migliore.

La seguente funzione avvolge una funzione asincrona esistente e crea una nuova funzione che regola automaticamente:

    // This routine makes a custom throttle wrapper for the async-function that's passed in.
    // A throttle function, when called, calls an async function (one that returns a promise)
    // in such a way to ensure the async function is only called when it's not already in progress.
    // in which case rather than calling it again, this routine returns the original promise to the caller.
    // If it's already in progress, then the original promise is returned instead.
    var makeThrottleFunction = function (asyncFunction) {
        var asyncPromise = null;
        return function (asyncInParam) {
            if (!asyncPromise) {
                var asyncDeferred = $q.defer();
                asyncPromise = asyncDeferred.promise;
                asyncFunction(asyncInParam).then(function (asyncOutParam) {
                    asyncDeferred.resolve(asyncOutParam);
                }).catch(function (parameters) {
                    return $q.reject(parameters);
                }).finally(function () {
                    asyncPromise = null;
                });
            }
            return asyncPromise;
        };
    };

Esempio di utilizzo:

var getCurrentUser = makeThrottleFunction(function() {
    // async call to server to get data
    // return promise..
});

Sono curioso di cosa hanno da dire gli esperti su questo approccio. Che cosa è un'alternativa, ecc. L'ho usato dappertutto nella mia app e funziona davvero bene. Inoltre, non credo che potrei farne a meno, il che solleva la domanda: se qualcosa del genere non è già presente nei framework, allora potrei mancare qualcosa di ovvio, come qualche impostazione o libreria che già fa questa cosa esatta. Quindi mi piacerebbe avere un feedback.

Sebbene io stia cercando le opinioni degli altri su come risolvere questo problema comune, al fine di soddisfare questo essere un Q & A, penso che una buona risposta sarebbe una delle seguenti: 1. Indica una biblioteca che fa questo tipo di cose. 2. Sottolinea che esiste un modo più semplice per farlo.

    
posta zumalifeguard 12.11.2014 - 18:13
fonte

1 risposta

5

Non sono a conoscenza di un'implementazione pubblicata di questa tecnica, ma è un modo abbastanza standard di usare le promesse. Un interessante effetto collaterale della componibilità, riusabilità ed estensibilità della programmazione funzionale è che spesso non si trovano cose nelle librerie che ci si aspetterebbe.

In un paradigma meno componibile, avresti bisogno di supporto in una libreria per fare qualcosa come la tua funzione di acceleratore, perché devi tessere il supporto per esso attraverso altre funzioni. Nel codice funzionale, i programmatori lo scrivono solo una volta, quindi possono riutilizzarlo ovunque.

Dopo aver scritto qualcosa del genere, con chi la condividi e come? È troppo piccolo per essere la sua biblioteca. È un po 'troppo specifico per non essere considerato gonfio in una biblioteca di promesse. Forse una sorta di libreria di utilità di promessa, ma poi sarebbe in un modulo non molto coeso con altre funzioni vagamente legate alle promesse, il che rende difficile trovarlo.

Quello che mi succede è che cerco per circa 10 minuti, quindi scrivo da solo perché mi ci vorrebbe solo mezz'ora. Lo metto in un file di "utilità" che tengo in giro con altre stranezze e funzioni che sono brevi ma altamente riutilizzabili, ma che non si adattano davvero da nessuna parte. Poi, sei mesi dopo, mi imbatto per caso in una biblioteca gestita dalla comunità con un nome strano.

Il mio punto è che, con la programmazione funzionale, non essere in grado di trovare un'implementazione esistente per qualcosa che sembra abbastanza "standard" non è un segno che altre persone lo stanno facendo in modo superiore, o che tu sei il primo a inventarlo. È solo un sintomo della difficoltà di condividere codice breve e riusabile in un modo che è facile da scoprire tramite una ricerca.

    
risposta data 12.11.2014 - 19:31
fonte