Ist es sicher, ein Versprechen mehrmals zu lösen?


113

Ich habe einen i18n-Dienst in meiner Anwendung, der den folgenden Code enthält:

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;
  };

Die Idee ist, dass der Benutzer anruft ensureLocaleIsLoadedund wartet, bis das Versprechen gelöst ist. Da der Zweck der Funktion jedoch darin besteht, nur sicherzustellen, dass das Gebietsschema geladen ist, ist es für den Benutzer vollkommen in Ordnung, es mehrmals aufzurufen.

Ich speichere derzeit nur ein einzelnes Versprechen und löse es, wenn der Benutzer die Funktion erneut aufruft, nachdem das Gebietsschema erfolgreich vom Server abgerufen wurde.

Soweit ich das beurteilen kann, funktioniert dies wie beabsichtigt, aber ich frage mich, ob dies ein angemessener Ansatz ist.



Ich habe es auch benutzt und es funktioniert gut.
Chandermani

Antworten:


117

Soweit ich derzeit Versprechen verstehe, sollte dies zu 100% in Ordnung sein. Das einzige, was zu verstehen ist, ist, dass, sobald es aufgelöst (oder abgelehnt) ist, es für ein zurückgestelltes Objekt ist - es getan wird.

Wenn Sie then(...)das Versprechen erneut einholen sollten, sollten Sie sofort das (erste) gelöste / abgelehnte Ergebnis erhalten.

Zusätzliche Anrufe an resolve()werden keine Wirkung haben (sollten nicht?). Ich bin mir nicht sicher, was passiert, wenn Sie versuchen, rejectein zuvor verschobenes Objekt zu verwenden resolved(ich vermute nichts).


27
Hier ist ein JSBin, der veranschaulicht, dass alles oben Genannte tatsächlich wahr ist: jsbin.com/gemepay/3/edit?js,console Nur die erste Auflösung wird jemals verwendet.
KONRAD

4
Hat jemand eine offizielle Dokumentation dazu gefunden? Es ist im Allgemeinen nicht ratsam, sich auf undokumentiertes Verhalten zu verlassen, selbst wenn es gerade funktioniert.
3ozän

ecma-international.org/ecma-262/6.0/#sec-promise.resolve - Ich habe bisher nichts gefunden, was besagt, dass es von Natur aus UNSICHER ist. Wenn Ihr Handler etwas tut, das eigentlich nur EINMAL ausgeführt werden sollte, muss er einen bestimmten Status überprüfen und aktualisieren, bevor er die Aktion erneut ausführt. Ich möchte aber auch, dass ein offizieller MDN-Eintrag oder ein Spezifikationsdokument absolute Klarheit erhält.
Demaniak

Ich kann auf der PromiseA + -Seite nichts "Beunruhigendes" sehen. Siehe versprechenaplus.com
demaniak

3
@demaniak Bei dieser Frage geht es um Versprechen / A + , nicht um ES6-Versprechen. Aber um Ihre Frage zu beantworten, den Teil des ES6 spec über Fremd Auflösungs- / ablehnen sicher zu sein , ist hier .
Trevor Robinson

1

Ich war vor einiger Zeit mit dem gleichen Problem konfrontiert, tatsächlich kann ein Versprechen nur einmal gelöst werden, ein weiterer Versuch wird nichts bewirken (kein Fehler, keine Warnung, kein thenAufruf).

Ich habe beschlossen, es so zu umgehen:

getUsers(users => showThem(users));

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

Übergeben Sie einfach Ihre Funktion als Rückruf und rufen Sie sie so oft auf, wie Sie möchten! Hoffe das macht Sinn.


Ich denke das ist falsch. Sie können das Versprechen einfach zurückgeben getUsersund es dann .then()so oft aufrufen , wie Sie möchten. Es ist nicht erforderlich, einen Rückruf weiterzuleiten. Meiner Meinung nach ist einer der Vorteile von Versprechungen, dass Sie den Rückruf nicht im Voraus angeben müssen.
John Henckel

@JohnHenckel Die Idee ist, das Versprechen mehrmals aufzulösen, dh Daten mehrmals zurückzugeben, ohne mehrere .thenAnweisungen zu haben. Ich denke, die einzige Möglichkeit, Daten mehrmals in den aufrufenden Kontext zurückzugeben, besteht darin, Rückrufe und keine Versprechen zu verwenden, da Versprechen nicht dafür erstellt wurden, auf diese Weise zu funktionieren.
T. Rex

0

Wenn Sie den Rückgabewert des Versprechens ändern müssen, geben Sie einfach einen neuen Wert zurück thenund verketten Sie next then/ catchon

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

Es gibt keine klare Möglichkeit, Versprechen mehrmals zu lösen, da sie erledigt sind, da sie gelöst wurden. Der bessere Ansatz hier ist, ein vom Beobachter beobachtbares Muster zu verwenden, zum Beispiel habe ich folgenden Code geschrieben, der das Socket-Client-Ereignis beobachtet. Sie können diesen Code erweitern, um Ihre Anforderungen zu erfüllen

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

Sie können Tests schreiben, um das Verhalten zu bestätigen.

Wenn Sie den folgenden Test ausführen, können Sie daraus schließen

Der Aufruf von Resolution () / Reject () löst niemals einen Fehler aus.

Nach dem Abwickeln (Abgelehnt) bleibt der aufgelöste Wert (Abgelehnter Fehler) unabhängig von den folgenden Auflösungs- () oder Ablehnungs- () Aufrufen erhalten.

Sie können auch meinen Blog-Beitrag für Details überprüfen .

/* 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

Was Sie tun sollten, ist ein ng-if an Ihrem Haupt-ng-Ausgang anzubringen und stattdessen einen Ladespinner zu zeigen. Sobald Ihr Gebietsschema geladen ist, zeigen Sie den Ausgang an und lassen die Komponentenhierarchie rendern. Auf diese Weise kann Ihre gesamte Anwendung davon ausgehen, dass das Gebietsschema geladen ist und keine Überprüfungen erforderlich sind.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.