Czy wielokrotne wypełnianie obietnicy jest bezpieczne?


115

W mojej aplikacji mam usługę i18n, która zawiera następujący kod:

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

Chodzi o to, że użytkownik dzwoniłby ensureLocaleIsLoadedi czekał, aż obietnica zostanie rozwiązana. Ale biorąc pod uwagę, że celem tej funkcji jest tylko zapewnienie załadowania ustawień regionalnych, byłoby w porządku, gdyby użytkownik wywołał ją kilka razy.

Obecnie przechowuję tylko jedną obietnicę i rozwiązuję ją, jeśli użytkownik ponownie wywoła funkcję po pomyślnym pobraniu ustawień regionalnych z serwera.

Z tego, co wiem, działa to zgodnie z przeznaczeniem, ale zastanawiam się, czy jest to właściwe podejście.



Ja też go użyłem i działa dobrze.
Chandermani

Odpowiedzi:


119

Jak obecnie rozumiem obietnice, powinno to być w 100% w porządku. Jedyną rzeczą do zrozumienia jest to, że raz rozwiązany (lub odrzucony), to znaczy dla odroczonego obiektu - jest zrobiony.

Jeśli then(...)ponownie skorzystasz z obietnicy, powinieneś natychmiast uzyskać (pierwszy) rozstrzygnięty / odrzucony wynik.

Dodatkowe wywołania do resolve()nie będą (nie powinny?) Mieć żadnego efektu. Nie jestem pewien, co się stanie, jeśli spróbujesz rejectodłożyć obiekt, który był wcześniej resolved(nic nie podejrzewam).


28
Oto JSBin ilustrujący, że wszystkie powyższe odpowiedzi są rzeczywiście prawdziwe: jsbin.com/gemepay/3/edit?js,console Tylko pierwsze rozwiązanie jest kiedykolwiek używane.
konrad

4
Czy ktoś znalazł jakąś oficjalną dokumentację na ten temat? Generalnie niewskazane jest poleganie na nieudokumentowanym zachowaniu, nawet jeśli teraz działa.
3ocen

ecma-international.org/ecma-262/6.0/#sec-promise.resolve - Jak dotąd nie znalazłem niczego, co stwierdza, że ​​jest to z natury NIEBEZPIECZNE. Jeśli twój program obsługi robi coś, co naprawdę powinno być zrobione tylko RAZ, chciałbym, żeby sprawdził i zaktualizował jakiś stan przed ponownym wykonaniem akcji. Ale chciałbym również, aby jakiś oficjalny wpis MDN lub dokument specyfikacji był absolutnie jasny.
demaniak

Nie widzę nic „niepokojącego” na stronie PromiseA +. Zobacz promisesaplus.com
demaniak

3
@demaniak To pytanie dotyczy obietnic / A + , a nie obietnic ES6. Ale odpowiadając na twoje pytanie, część specyfikacji ES6 dotycząca bezpieczeństwa rozwiązania / odrzucenia jest tutaj .
Trevor Robinson

1

Spotkałem się z tym samym jakiś czas temu, rzeczywiście, obietnicę można spełnić tylko raz, kolejne próby nic nie dadzą (bez błędu, bez ostrzeżenia, bez thenwezwania).

Postanowiłem to obejść w ten sposób:

getUsers(users => showThem(users));

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

po prostu przekaż swoją funkcję jako wywołanie zwrotne i wywołuj ją tyle razy, ile chcesz! Mam nadzieję, że to ma sens.


Myślę, że to źle. Możesz po prostu zwrócić obietnicę, getUsersa następnie przywoływać .then()ją tyle razy, ile chcesz. Nie ma potrzeby przekazywania oddzwonienia. Moim zdaniem jedną z zalet obietnic jest to, że nie musisz z góry określać wywołania zwrotnego.
John Henckel

@JohnHenckel Chodzi o to, aby wielokrotnie rozwiązać obietnicę, tj. Wielokrotnie zwracać dane, a nie składać wielu .theninstrukcji. Jeśli chodzi o to, co jest warte, myślę, że jedynym sposobem na wielokrotne zwracanie danych do kontekstu wywołania jest użycie wywołań zwrotnych, a nie obietnic, ponieważ obietnice nie zostały zbudowane tak, aby działały w ten sposób.
T. Rex

0

Jeśli chcesz zmienić zwracaną wartość obietnicy, po prostu zwróć nową wartość theni połącz ją następną then/ catchna niej

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

Nie ma jasnego sposobu na wielokrotne spełnianie obietnic, ponieważ skoro zostały rozwiązane, to już koniec. Lepszym podejściem jest tutaj użycie wzorca obserwowalnego przez obserwatora, na przykład napisałem następujący kod, który obserwuje zdarzenie klienta gniazda. Możesz rozszerzyć ten kod zgodnie ze swoimi potrzebami

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

Możesz napisać testy, aby potwierdzić zachowanie.

Wykonując następujący test, możesz to stwierdzić

Wywołanie resolution () / odrzucenie () nigdy nie zgłasza błędu.

Po rozliczeniu (odrzuceniu) rozwiązana wartość (odrzucony błąd) zostanie zachowana bez względu na następujące po niej wywołania resell () lub disable ().

Możesz również sprawdzić mój wpis na blogu, aby uzyskać szczegółowe informacje.

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

Powinieneś umieścić ng-if na swoim głównym wylocie ng-if i pokazać zamiast tego spinner ładujący. Po załadowaniu ustawień regionalnych wyświetlasz gniazdko i pozwalasz na renderowanie hierarchii komponentów. W ten sposób cała aplikacja może założyć, że ustawienia regionalne są załadowane i żadne kontrole nie są konieczne.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.