È sicuro risolvere una promessa più volte?


115

Ho un servizio i18n nella mia applicazione che contiene il seguente codice:

var i18nService = function() {
  this.ensureLocaleIsLoaded = function() {
    if( !this.existingPromise ) {
      this.existingPromise = $q.defer();

      var deferred = this.existingPromise;
      var userLanguage = $( "body" ).data( "language" );
      this.userLanguage = userLanguage;

      console.log( "Loading locale '" + userLanguage + "' from server..." );
      $http( { method:"get", url:"/i18n/" + userLanguage, cache:true } ).success( function( translations ) {
        $rootScope.i18n = translations;
        deferred.resolve( $rootScope.i18n );
      } );
    }

    if( $rootScope.i18n ) {
      this.existingPromise.resolve( $rootScope.i18n );
    }

    return this.existingPromise.promise;
  };

L'idea è che l'utente chiami ensureLocaleIsLoadede aspetti che la promessa venga risolta. Ma dato che lo scopo della funzione è solo quello di garantire che la localizzazione venga caricata, sarebbe perfettamente corretto che l'utente la richiamasse più volte.

Al momento sto solo memorizzando una singola promessa e la risolvo se l'utente chiama di nuovo la funzione dopo che la locale è stata recuperata con successo dal server.

Da quello che posso dire, funziona come previsto, ma mi chiedo se sia un approccio corretto.



Anch'io l'ho usato e funziona bene.
Chandermani

Risposte:


119

A quanto ho capito al momento, dovrebbe andare bene al 100%. L'unica cosa da capire è che una volta risolto (o rifiutato), è per un oggetto differito - è fatto.

Se dovessi richiamare then(...)nuovamente la sua promessa, dovresti immediatamente ottenere il (primo) risultato risolto / rifiutato.

Chiamate aggiuntive a resolve()non avranno (non dovrebbero?) Alcun effetto. Non sono sicuro di cosa succede se si tenta di rejectun oggetto differito che era in precedenza resolved(non sospetto nulla).


28
Ecco un JSBin che illustra che tutto quanto sopra è effettivamente vero: jsbin.com/gemepay/3/edit?js,console Viene utilizzata solo la prima risoluzione.
konrad

4
Qualcuno ha trovato documentazione ufficiale su questo? In genere è sconsigliabile fare affidamento su comportamenti non documentati anche se in questo momento funziona.
3ocene

ecma-international.org/ecma-262/6.0/#sec-promise.resolve - Finora non ho trovato nulla che affermi che è intrinsecamente NON SICURO. Se il tuo gestore fa qualcosa che dovrebbe essere fatto solo UNA VOLTA, lo farei controllare e aggiornare uno stato prima di eseguire nuovamente l'azione. Ma vorrei anche una voce MDN ufficiale o un documento specifico per ottenere una chiarezza assoluta.
demaniak

Non riesco a vedere nulla di "preoccupante" nella pagina PromiseA +. Vedi promisesaplus.com
demaniak

3
@demaniak Questa domanda riguarda le promesse / A + , non le promesse di ES6. Ma per rispondere alla tua domanda, la parte della specifica ES6 sulla sicurezza estranea di risoluzione / rifiuto è qui .
Trevor Robinson

1

Ho affrontato la stessa cosa qualche tempo fa, infatti una promessa può essere risolta solo una volta, un altro tentativo non farà nulla (nessun errore, nessun avviso, nessuna theninvocazione).

Ho deciso di risolverlo in questo modo:

getUsers(users => showThem(users));

getUsers(callback){
    callback(getCachedUsers())
    api.getUsers().then(users => callback(users))
}

passa la tua funzione come callback e invocala tutte le volte che vuoi! Spero che abbia un senso.


Penso che questo sia sbagliato. Potresti semplicemente restituire la promessa da getUserse poi invocarla .then()tutte le volte che vuoi. Non è necessario passare una richiamata. Secondo me uno dei vantaggi delle promesse è che non è necessario specificare la richiamata in anticipo.
John Henckel

@JohnHenckel L'idea è di risolvere la promessa più volte, ovvero restituire i dati più volte, non avere più .thendichiarazioni. Per quello che vale, penso che l'unico modo per restituire i dati più volte al contesto chiamante sia usare i callback e non le promesse, poiché le promesse non sono state costruite per funzionare in quel modo.
T. Rex,

0

Se è necessario modificare il valore di ritorno della promessa, è sufficiente restituire un nuovo valore thene concatenare il successivo then/ catchsu di esso

var p1 = new Promise((resolve, reject) => { resolve(1) });
    
var p2 = p1.then(v => {
  console.log("First then, value is", v);
  return 2;
});
    
p2.then(v => {
  console.log("Second then, value is", v);
});


0

Non esiste un modo chiaro per risolvere le promesse più volte perché da quando è stato risolto è stato fatto. L'approccio migliore qui è usare il modello osservabile dall'osservatore, ad esempio ho scritto il seguente codice che osserva l'evento del client socket. Puoi estendere questo codice per soddisfare le tue esigenze

const evokeObjectMethodWithArgs = (methodName, args) => (src) => src[methodName].apply(null, args);
    const hasMethodName = (name) => (target = {}) => typeof target[name] === 'function';
    const Observable = function (fn) {
        const subscribers = [];
        this.subscribe = subscribers.push.bind(subscribers);
        const observer = {
            next: (...args) => subscribers.filter(hasMethodName('next')).forEach(evokeObjectMethodWithArgs('next', args))
        };
        setTimeout(() => {
            try {
                fn(observer);
            } catch (e) {
                subscribers.filter(hasMethodName('error')).forEach(evokeObjectMethodWithArgs('error', e));
            }
        });

    };

    const fromEvent = (target, eventName) => new Observable((obs) => target.on(eventName, obs.next));

    fromEvent(client, 'document:save').subscribe({
        async next(document, docName) {
            await writeFilePromise(resolve(dataDir, `${docName}`), document);
            client.emit('document:save', document);
        }
    });

0

È possibile scrivere test per confermare il comportamento.

Eseguendo il seguente test puoi concludere che

La chiamata alla risoluzione () / rifiuto () non genera mai errori.

Una volta risolto (rifiutato), il valore risolto (errore rifiutato) verrà conservato indipendentemente dalle seguenti chiamate di risoluzione () o rifiuto ().

Puoi anche controllare il mio post sul blog per i dettagli.

/* eslint-disable prefer-promise-reject-errors */
const flipPromise = require('flip-promise').default

describe('promise', () => {
    test('error catch with resolve', () => new Promise(async (rs, rj) => {
        const getPromise = () => new Promise(resolve => {
            try {
                resolve()
            } catch (err) {
                rj('error caught in unexpected location')
            }
        })
        try {
            await getPromise()
            throw new Error('error thrown out side')
        } catch (e) {
            rs('error caught in expected location')
        }
    }))
    test('error catch with reject', () => new Promise(async (rs, rj) => {
        const getPromise = () => new Promise((_resolve, reject) => {
            try {
                reject()
            } catch (err) {
                rj('error caught in unexpected location')
            }
        })
        try {
            await getPromise()
        } catch (e) {
            try {
                throw new Error('error thrown out side')
            } catch (e){
                rs('error caught in expected location')
            }
        }
    }))
    test('await multiple times resolved promise', async () => {
        const pr = Promise.resolve(1)
        expect(await pr).toBe(1)
        expect(await pr).toBe(1)
    })
    test('await multiple times rejected promise', async () => {
        const pr = Promise.reject(1)
        expect(await flipPromise(pr)).toBe(1)
        expect(await flipPromise(pr)).toBe(1)
    })
    test('resolve multiple times', async () => {
        const pr = new Promise(resolve => {
            resolve(1)
            resolve(2)
            resolve(3)
        })
        expect(await pr).toBe(1)
    })
    test('resolve then reject', async () => {
        const pr = new Promise((resolve, reject) => {
            resolve(1)
            resolve(2)
            resolve(3)
            reject(4)
        })
        expect(await pr).toBe(1)
    })
    test('reject multiple times', async () => {
        const pr = new Promise((_resolve, reject) => {
            reject(1)
            reject(2)
            reject(3)
        })
        expect(await flipPromise(pr)).toBe(1)
    })

    test('reject then resolve', async () => {
        const pr = new Promise((resolve, reject) => {
            reject(1)
            reject(2)
            reject(3)
            resolve(4)
        })
        expect(await flipPromise(pr)).toBe(1)
    })
test('constructor is not async', async () => {
    let val
    let val1
    const pr = new Promise(resolve => {
        val = 1
        setTimeout(() => {
            resolve()
            val1 = 2
        })
    })
    expect(val).toBe(1)
    expect(val1).toBeUndefined()
    await pr
    expect(val).toBe(1)
    expect(val1).toBe(2)
})

})

-1

Quello che dovresti fare è mettere un ng-if nella tua presa ng principale e mostrare invece uno spinner di caricamento. Una volta caricate le impostazioni locali, viene visualizzato l'outlet e si lascia che la gerarchia dei componenti venga visualizzata. In questo modo tutta la tua applicazione può presumere che la locale sia caricata e non sono necessari controlli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.