Чи безпечно вирішити обіцянку кілька разів?


115

У моїй програмі є служба i18n, яка містить такий код:

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

Ідея полягає в тому, щоб користувач зателефонував ensureLocaleIsLoadedі чекав, коли обіцянка буде вирішена. Але враховуючи, що мета функції полягає лише у тому, щоб завантажувати локал, користувачеві було б цілком чудово викликати його декілька разів.

Наразі я просто зберігаю єдину обіцянку і вирішую її, якщо користувач знову зателефонує до функції після успішного вибору локалу з сервера.

З того, що я можу сказати, це працює за призначенням, але мені цікаво, чи це правильний підхід.


7
Дивіться цю відповідь .
robertklep

Я теж його використав, і він чудово працює.
Chandermani

Відповіді:


119

Наскільки я зараз розумію обіцянки, це має бути на 100% штрафом. Єдине, що потрібно зрозуміти, що колись вирішено (або відхилено), тобто це для об'єкта, що перешкоджає, - це робиться.

Якщо ви знову закликаєте then(...)це обіцяти, вам слід негайно отримати (перший) вирішений / відхилений результат.

Додаткові дзвінки до resolve()не матимуть (не повинні?) Ніякого ефекту. Не впевнений, що станеться, якщо ви намагаєтесь rejectзахистити об'єкт, який раніше був resolved(я нічого не підозрюю).


28
Ось JSBin, який ілюструє, що все вищезазначене насправді є істинним: jsbin.com/gemepay/3/edit?js,console Використовується тільки перше рішення.
конрад

4
Хтось знайшов офіційну документацію з цього приводу? Як правило, не доцільно покладатися на незадокументовану поведінку, навіть якщо це працює зараз.
3оцен

ecma-international.org/ecma-262/6.0/#sec-promise.resolve - На сьогоднішній день я не знайшов нічого, що свідчить про те, що це по суті UNSAFE. Якщо ваш обробник робить щось, що дійсно повинно бути зроблено лише ТАКЕ, я б його перевірив і оновив деякий стан, перш ніж повторити дію. Але я також хотів би, щоб якийсь офіційний запис MDN або специфікація doc отримати абсолютну чіткість.
деманяк

Я не бачу нічого «тривожного» на сторінці PromiseA +. Див promisesaplus.com
demaniak

3
@demaniak Це питання стосується обіцянок / A + , а не обіцянок ES6. Але щоб відповісти на ваше запитання, то частина ES6 специфікації про сторонньої рішучості / відхиляти бути безпечно тут .
Тревор Робінсон

1

Я зіткнувся з тим же, що давно, і справді обіцянку можна вирішити лише один раз, інша спроба нічого не зробить (ні помилки, ні попередження, ні thenвиклику).

Я вирішив обробити це так:

getUsers(users => showThem(users));

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

просто передайте свою функцію як зворотний дзвінок і викликайте її стільки разів, скільки захочете! Сподіваюся, що це має сенс.


Я думаю, що це неправильно. Ви можете просто повернути обіцянку getUsersі потім покликатись .then()на цю обіцянку стільки разів, скільки вам захочеться. Не потрібно передавати зворотний дзвінок. На мою думку, однією з переваг обіцянок є те, що вам не потрібно вказувати зворотній дзвінок вперед.
Джон Генкель

@JohnHenckel Ідея полягає в тому, щоб вирішити обіцянку кілька разів, тобто повернути дані кілька разів, не мати декількох .thenзаяв. Зважаючи на те, що варто, я думаю, що єдиний спосіб повернути дані кілька разів у контекст виклику - це використовувати зворотні дзвінки, а не обіцянки, оскільки обіцянки не були побудовані для роботи таким чином.
Т. Рекс

0

Якщо вам потрібно змінити повернену величину обіцянки, просто поверніть нове значення в thenланцюг і наступне then/ catchна ньому

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

Немає чіткого способу вирішення обіцянок кілька разів, оскільки, оскільки це вирішено, це зроблено. Кращим підходом тут є використання шаблону, що спостерігається спостерігачем, наприклад, я написав наступний код, який спостерігає за подією клієнта сокета. Ви можете розширити цей код відповідно до своїх потреб

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

Ви можете написати тести для підтвердження поведінки.

Запустивши наступний тест, ви можете зробити висновок про це

Виклик рішення () / відхилити () ніколи не видає помилку.

Після вирішення (відхилення) вирішене значення (відхилена помилка) буде збережено незалежно від наступних дзвінків на вирішення () або відхилення ().

Ви також можете перевірити мій пост у блозі для деталей.

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

Що ви повинні зробити, це поставити ng-if у вашому головному ng-розетці і показати замість нього завантажувальний спінер. Після завантаження вашої локальної програми ви показуєте розетку і дозволяєте ієрархії компонентів візуалізувати. Таким чином, у всій вашій програмі можна вважати, що мова завантажена і перевірки не потрібні.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.