多次兑现诺言是否安全?


114

我的应用程序中有一个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并等待诺言被解决。但是,鉴于该功能的目的仅是确保加载了语言环境,因此用户多次调用它是完全可以的。

我目前仅存储一个promise,如果在从服务器成功检索到区域设置后用户再次调用该函数,则将其解决。

据我所知,这是按预期工作的,但是我想知道这是否是正确的方法。


7

我也用过它,并且效果很好。
Chandermani

Answers:


117

据我了解,目前的承诺应为100%罚款。唯一需要了解的是,一旦解决(或拒绝)了,就对一个延迟的对象进行了处理。

如果您then(...)再次要求履行承诺,则应立即获得(第一个)解决/拒绝的结果。

附加调用resolve()will不会(应该吗?)没有任何效果。不知道如果尝试使用以前reject的延迟对象会发生什么resolved(我怀疑没有)。


27
这是一个JSBin,说明上述所有内容实际上都是正确的:jsbin.com/gemepay/3/edit?js,console仅使用第一个解析。
konrad '17

4
有人找到有关此的任何官方文档吗?通常建议不要依赖未记录的行为,即使它现在可以正常工作。
3ocene

ecma-international.org/ecma-262/6.0/#sec-promise.resolve-到目前为止,我还没有发现任何声明它本质上是不安全的东西。如果您的处理程序确实只应执行一次操作,那么我将让它检查并更新某些状态,然后再次执行操作。但我也想获得一些官方的MDN条目或规范文档以获取绝对的清晰度。
demaniak

我在PromiseA +页面中看不到任何“麻烦”的内容。参见promisesaplus.com
demaniak's

3
@demaniak这个问题是关于Promises / A +的,而不是ES6的诺言。但是要回答您的问题,此处是ES6规范中有关外部解决/拒绝安全的部分。
特雷弗·罗宾逊

1

我前段时间遇到过同样的事情,确实一个诺言只能解决一次,而另一个尝试则什么也没有做(没有错误,没有警告,没有then调用)。

我决定像这样解决它:

getUsers(users => showThem(users));

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

只需将您的函数作为回调传递,并根据需要多次调用它即可!希望有道理。


我认为这是错误的。您可以简单地从中返回承诺getUsers,然后根据需要调用.then()该承诺多次。无需传递回调。在我看来,promise的优点之一是您不需要预先指定回调。
约翰·亨克尔

@JohnHenckel的想法是多次解决承诺,即多次返回数据,没有多个 .then语句。对于它的价值,我认为多次将数据返回到调用上下文的唯一方法是使用回调而不是promise,因为promise并不是以这种方式工作的。
T. Rex

0

如果您需要更改promise的返回值,只需返回新值then并在其上链接next 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

您可以编写测试以确认行为。

通过运行以下测试,您可以得出结论:

resolve()/ reject()调用永远不会抛出错误。

一旦解决(拒绝),无论随后的resolve()或reject()调用如何,都将保留已解决的值(拒绝的错误)。

您也可以查看我的博客文章以了解详细信息。

/* 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出口上放置ng-if,然后显示加载微调框。加载语言环境后,将显示出口,并渲染组件层次结构。这样,您的所有应用程序都可以假定已加载语言环境,而无需进行检查。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.