多次解决承诺是否安全?

问题描述 投票:0回答:8

我的应用程序中有一个 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
并等待承诺得到解决。但鉴于该函数的目的只是确保加载区域设置,因此用户多次调用它是完全可以的。

我目前只存储一个承诺,并在用户从服务器成功检索区域设置后再次调用该函数时解决它。

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

javascript angularjs promise
8个回答
215
投票

据我目前的理解,这应该是 100% 没问题。唯一要理解的是,一旦解决(或拒绝),这就是延迟对象 - 它就完成了。

如果您再次调用

then(...)
的承诺,您将立即获得(第一个)已解决/拒绝的结果。

额外调用

resolve()
不会有任何效果。

下面是涵盖这些用例的可执行代码片段:

var p = new Promise((resolve, reject) => {
  resolve(1);
  reject(2);
  resolve(3);
});

p.then(x => console.log('resolved to ' + x))
 .catch(x => console.log('never called ' + x));

p.then(x => console.log('one more ' + x));
p.then(x => console.log('two more ' + x));
p.then(x => console.log('three more ' + x));


1
投票

没有明确的方法可以多次解决承诺,因为既然解决了,那就完成了。这里更好的方法是使用观察者可观察模式,例如我编写了以下观察套接字客户端事件的代码。您可以扩展此代码以满足您的需要

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
投票

我不久前也遇到过同样的事情,确实一个承诺只能解决一次,另一次尝试将什么也不做(没有错误,没有警告,没有

then
调用)。

我决定像这样解决这个问题:

getUsers(users => showThem(users));

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

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


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


-1
投票

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

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

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

})

-2
投票

你应该做的是在你的主 ng-outlet 上放置一个 ng-if 并显示一个加载旋转器。加载区域设置后,您将显示出口并让组件层次结构呈现。这样,您的所有应用程序都可以假设区域设置已加载,并且无需进行检查。


-2
投票

不。多次解决/拒绝承诺是不安全的。它基本上是一个错误,很难发现,因为它并不总是可重现的。

有一些模式可用于在调试时跟踪此类问题。关于这个主题的精彩演讲:Ruben Bridgewater — 错误处理:正确执行!(与问题相关的部分大约 40 分钟)


-2
投票

参见github要点:reuse_promise.js

/*
reuse a promise for multiple resolve()s since promises only resolve once and then never again
*/

import React, { useEffect, useState } from 'react'

export default () => {
    
    const [somePromise, setSomePromise] = useState(promiseCreator())
        
    useEffect(() => {
        
        somePromise.then(data => {
            
            // do things here
            
            setSomePromise(promiseCreator())
        })
        
    }, [somePromise])
}

const promiseCreator = () => {
    return new Promise((resolve, reject) => {
        // do things
        resolve(/*data*/)
    })
}
© www.soinside.com 2019 - 2024. All rights reserved.