多次解决一个承诺是否安全?

我的应用程序中有一个 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并等待承诺得到解决。但是如果函数的目的只是为了加载语言环境的 确保,那么用户可以多次调用它。

我目前只是存储一个承诺,如果在成功地从服务器检索到区域设置之后,用户再次调用该函数,我将解决这个问题。

据我所知,这是预期的效果,但我想知道这是否是一个合适的方法。

96951 次浏览

据我所知,目前的承诺,这应该是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));

我前段时间也遇到过同样的情况,确实一个承诺只能解决一次,再试一次也不会有什么结果(没有错误,没有警告,没有 then调用)。

我决定这样解决:

getUsers(users => showThem(users));


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

只需将函数作为回调函数传递,然后随意多次调用它就可以了! 希望这样做有意义。

你应该做的是把一个 ng- 如果在您的主 ng- 出口,并显示一个加载纺纱器而不是。一旦您的区域设置被加载,您显示出口,并让组件层次结构呈现。通过这种方式,您的所有应用程序都可以假设语言环境已经加载,不需要进行检查。

如果需要更改承诺的返回值,只需在 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);
});

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

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

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

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

解析()/拒绝()调用 never throw 错误。

一旦解决(拒绝) ,解决值(拒绝错误)将被保留 无论以下是解决()还是拒绝()调用。

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

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


})

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

有一种模式可用于在调试时跟踪此类问题。关于这个主题的精彩演讲: 鲁本 · 布里奇沃特ーー错误处理: 正确处理!(与这个问题相关的部分大约40分钟)

请参阅 github 要点:

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