Apakah aman untuk menyelesaikan janji beberapa kali?


115

Saya memiliki layanan i18n di aplikasi saya yang berisi kode berikut:

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

Idenya adalah bahwa pengguna akan menelepon ensureLocaleIsLoadeddan menunggu janji diselesaikan. Tetapi mengingat bahwa tujuan dari fungsi ini hanya untuk memastikan bahwa lokal dimuat, akan sangat baik bagi pengguna untuk memanggilnya beberapa kali.

Saat ini saya hanya menyimpan satu janji dan menyelesaikannya jika pengguna memanggil fungsi itu lagi setelah lokal berhasil diambil dari server.

Dari apa yang saya tahu, ini berfungsi sebagaimana mestinya, tetapi saya bertanya-tanya apakah ini pendekatan yang tepat.



Saya juga telah menggunakannya dan berfungsi dengan baik.
Chandermani

Jawaban:


119

Seperti yang saya pahami tentang janji saat ini, ini seharusnya 100% baik-baik saja. Satu-satunya hal yang perlu dipahami adalah bahwa setelah diselesaikan (atau ditolak), itu untuk objek yang ditangguhkan - selesai.

Jika Anda harus then(...)menepati janjinya lagi, Anda harus segera mendapatkan hasil (pertama) terselesaikan / ditolak.

Panggilan tambahan ke resolve()tidak akan (seharusnya tidak?) Berpengaruh. Tidak yakin apa yang terjadi jika Anda mencoba rejectobjek yang ditangguhkan sebelumnya resolved(saya tidak curiga apa-apa).


28
Berikut JSBin yang menggambarkan bahwa semua hal di atas sebenarnya benar: jsbin.com/gemepay/3/edit?js,console Hanya penyelesaian pertama yang pernah digunakan.
konrad

4
Adakah yang menemukan dokumentasi resmi tentang ini? Biasanya tidak disarankan untuk mengandalkan perilaku tidak berdokumen bahkan jika itu berfungsi sekarang.
3ocene

ecma-international.org/ecma-262/6.0/#sec-promise.resolve - Sampai saat ini saya tidak menemukan apa pun yang menyatakan bahwa ini pada dasarnya TIDAK AMAN. Jika penangan Anda melakukan sesuatu yang seharusnya hanya dilakukan SEKALI, saya akan memeriksanya dan memperbarui beberapa status sebelum melakukan tindakan lagi. Tetapi saya juga ingin beberapa entri MDN resmi atau dokumen spesifikasi mendapatkan kejelasan mutlak.
demaniak

Saya tidak dapat melihat apa pun yang "mengganggu" di halaman PromiseA +. Lihat promisesaplus.com
demaniak

3
@demaniak Pertanyaan ini tentang Janji / A + , bukan janji ES6. Tetapi untuk menjawab pertanyaan Anda, bagian dari spesifikasi ES6 tentang penyelesaian / penolakan asing menjadi aman ada di sini .
Trevor Robinson

1

Saya menghadapi hal yang sama beberapa waktu yang lalu, memang janji hanya bisa diselesaikan sekali, percobaan lain tidak akan melakukan apa-apa (tidak ada kesalahan, tidak ada peringatan, tidak ada thenpermintaan).

Saya memutuskan untuk mengatasinya seperti ini:

getUsers(users => showThem(users));

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

cukup teruskan fungsi Anda sebagai panggilan balik dan panggil sesering yang Anda inginkan! Harapan itu masuk akal.


Saya pikir ini salah. Anda bisa membalas janji itu getUsersdan kemudian .then()menepati janji itu sebanyak yang Anda inginkan. Tidak perlu meneruskan panggilan balik. Menurut pendapat saya, salah satu keuntungan dari promise adalah Anda tidak perlu menentukan callback di muka.
John Henckel

@JohnHenckel Idenya adalah untuk menyelesaikan janji beberapa kali, yaitu mengembalikan data beberapa kali, tidak memiliki banyak .thenpernyataan. Untuk apa nilainya, saya pikir satu-satunya cara untuk mengembalikan data beberapa kali ke konteks panggilan adalah dengan menggunakan callback dan bukan janji, karena janji tidak dibuat untuk berfungsi dengan cara itu.
T.Rex

0

Jika Anda perlu mengubah nilai pengembalian janji, cukup kembalikan nilai baru thendan rantai berikutnya then/ catchdi atasnya

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

Tidak ada cara yang jelas untuk menyelesaikan janji berkali-kali karena sejak sudah terselesaikan maka sudah selesai. Pendekatan yang lebih baik di sini adalah dengan menggunakan pola pengamat-pengamatan misalnya saya menulis kode berikut yang mengamati acara klien soket. Anda dapat memperpanjang kode ini untuk memenuhi kebutuhan Anda

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

Anda dapat menulis tes untuk mengonfirmasi perilaku tersebut.

Dengan menjalankan tes berikut, Anda dapat menyimpulkan bahwa

Panggilan menyelesaikan () / menolak () tidak pernah melempar kesalahan.

Setelah diselesaikan (ditolak), nilai yang diselesaikan (kesalahan yang ditolak) akan dipertahankan terlepas dari panggilan menyelesaikan () atau menolak () berikut.

Anda juga dapat memeriksa posting blog saya untuk detailnya.

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

Yang harus Anda lakukan adalah meletakkan ng-if pada outlet-ng utama Anda dan menampilkan pemintal pemuatan sebagai gantinya. Setelah lokal Anda dimuat, Anda menunjukkan outlet dan membiarkan hierarki komponen dirender. Dengan cara ini, semua aplikasi Anda dapat mengasumsikan bahwa lokal telah dimuat dan tidak perlu pemeriksaan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.