Bir sözü birden çok kez çözmek güvenli midir?


115

Uygulamamda aşağıdaki kodu içeren bir i18n hizmetim var:

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

Buradaki fikir, kullanıcının arayıp ensureLocaleIsLoadedsözün çözülmesini beklemesidir. Ancak, işlevin amacının yalnızca yerel ayarın yüklenmesini sağlamak olduğu düşünüldüğünde, kullanıcının onu birkaç kez çağırması mükemmel olacaktır.

Şu anda sadece tek bir söz depoluyorum ve yerel ayar sunucudan başarıyla alındıktan sonra kullanıcı işlevi tekrar çağırırsa çözüyorum.

Anladığım kadarıyla, bu amaçlandığı gibi çalışıyor, ancak bunun uygun bir yaklaşım olup olmadığını merak ediyorum.


7
Bu cevaba bakın .
robertklep

Ben de kullandım ve iyi çalışıyor.
Chandermani

Yanıtlar:


119

Şu anda verdiğim sözleri anladığım kadarıyla, bu% 100 iyi olmalı. Anlaşılması gereken tek şey, bir kez çözüldüğünde (veya reddedildiğinde), yani ertelenmiş bir nesne içindir - yapılır.

then(...)Sözünü tekrar çağırırsanız , derhal (ilk) çözülen / reddedilen sonucu almalısınız.

Ek çağrıların resolve()herhangi bir etkisi olmayacak (olmamalı mı?). Daha rejectönce ertelenmiş bir nesneye çalışırsanız ne olacağından emin değilim resolved(hiçbir şeyden şüphelenmiyorum).


28
İşte yukarıdakilerin hepsinin gerçekten doğru olduğunu gösteren bir JSBin: jsbin.com/gemepay/3/edit?js,console Sadece ilk çözümleme kullanıldı.
konrad

4
Bununla ilgili herhangi bir resmi belge bulan oldu mu? Şu anda işe yarasa bile, belgelenmemiş davranışlara güvenmek genellikle tavsiye edilmez.
3ocene

ecma-international.org/ecma-262/6.0/#sec-promise.resolve - Şimdiye kadar doğası gereği GÜVENLİ OLMADIĞINI belirten hiçbir şey bulamadım. İşleyiciniz gerçekten yalnızca BİR KEZ yapılması gereken bir şey yaparsa, eylemi tekrar gerçekleştirmeden önce bazı durumları kontrol edip güncellemesini isterim. Ancak, mutlak netlik elde etmek için bazı resmi MDN girişlerini veya teknik özellik belgelerini de istiyorum.
demaniak

PromiseA + sayfasında "rahatsız edici" bir şey göremiyorum. See promisesaplus.com
demaniak

3
@demaniak Bu soru Promises / A + ile ilgili , ES6 vaatleri değil. Ancak sorunuza cevap vermek için, ES6 şartnamesinin gereksiz çözme / reddetmenin güvenli olduğu kısmı burada .
Trevor Robinson

1

Bir süre önce aynı şeyle karşılaştım, aslında bir söz sadece bir kez çözülebilir, başka bir deneme hiçbir şey yapmaz (hata yok, uyarı yok, thençağrı yok ).

Bunu şu şekilde çözmeye karar verdim:

getUsers(users => showThem(users));

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

işlevinizi bir geri arama olarak iletin ve istediğiniz kadar çağırın! Umarım mantıklıdır.


Bence bu yanlış. Sözü yerine getirebilir getUsersve sonra bu sözde .then()istediğiniz kadar başvurabilirsiniz. Geri aramayı geçmeye gerek yoktur. Bana göre vaatlerin avantajlarından biri geri aramayı önceden belirtmenize gerek olmamasıdır.
John Henckel

@JohnHenckel Buradaki fikir, sözü birden çok kez çözmektir, yani verileri birden çok kez döndürmek, birden fazla .thenifade içermemek . Ne olursa olsun, verileri arama bağlamına birden çok kez döndürmenin tek yolunun sözler değil geri aramaları kullanmak olduğunu düşünüyorum, çünkü sözler bu şekilde çalışmak için oluşturulmamıştı.
T.Rex

0

Sözün dönüş değerini değiştirmeniz gerekirse, yeni değeri döndürün thenve sonraki then/ catchüzerine zincirleyin

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

Verilen sözleri birden çok kez çözmenin net bir yolu yoktur çünkü çözüldüğü için yerine getirilmiştir. Buradaki daha iyi yaklaşım, gözlemci tarafından gözlemlenebilir desen kullanmaktır, örneğin soket istemci olayını gözlemleyen aşağıdaki kodu yazdım. İhtiyaçlarınızı karşılamak için bu kodu genişletebilirsiniz

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

Davranışı doğrulamak için testler yazabilirsiniz.

Aşağıdaki testi çalıştırarak şu sonuca varabilirsiniz:

Resolver () / reject () çağrısı asla hata vermez.

Çözüldükten (reddedildi) sonra, çözümlenen değer (reddedilen hata), aşağıdaki resol () veya reject () çağrılarına bakılmaksızın korunur.

Ayrıntılar için blog yazıma da bakabilirsiniz.

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

Yapmanız gereken, ana ng çıkışınıza bir ng-if koymak ve bunun yerine bir yükleme döndürücüsü göstermektir. Yerel ayarınız yüklendikten sonra çıkışı gösterirsiniz ve bileşen hiyerarşisinin işlenmesine izin verirsiniz. Bu şekilde, tüm uygulamanız yerel ayarın yüklendiğini ve herhangi bir denetime gerek olmadığını varsayabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.