如何实现重复的承诺调用链?



请知道我不是在寻找reduce模式,它假设我已经需要事先知道执行次数。这里的递归性也是矫枉过正。

我需要执行一个连续返回承诺(已解决/拒绝(的调用,直到在该承诺的返回数据中我找到某个关键字,伪代码:

let response_that_will_come_from_each_promise = null;
do {
response_that_will_come_from_each_promise = execute_my_call_that_is_a_promise();
} while(!('finished_all_calls' in response_that_will_come_from_each_promise ));

不幸的是,这不适用于承诺的异步性质。for 循环也不会这样做,因为dofor都是同步的。

简而言之,我怎样才能继续调用同一个函数,该函数也恰好返回 N 次承诺,直到我检测到有问题?

while可以在async function内异步:

async function pollStuff() {
while(!("keyword" in await somePromise());
}

没有异步/等待递归是你的朋友:

const pollStuff = () => 
somePromise().then(result => "keyword" in result ? "done" : pollStuff())

答案:

为了可重用和维护,您可以创建一个构造函数来为您执行此操作:

function PromiseUntil(promiseFn, conditional) {
let trace = (place) => e => console.log(`trace: ${place} :: ${e}`);
return {
[Symbol.asyncIterator]: async function*() {
let p;
while(p != conditional) {  p = await promiseFn().catch(trace("PromiseUntil[Symbol.asyncIterator]")) }
yield p;
},
resolve: async function(cb = i => i, err = trace("PromiseUntil.resolve")) {
this.resolve = false;
try {
for await (let p of this) {
this.resolved = true;
return cb(p);
}
} catch (e) {
err(e);
}
}
}
}

它只是接受一个 promise 函数并重复它,直到解析的 Promise 返回的值与条件匹配。在下面,它检查它是否等于"awaited value".一旦匹配,就会返回它,并使用该值调用回调函数(在示例中console.log(。


例:

function PromiseUntil(promiseFn, conditional) {
let trace = (place) => e => console.log(`trace: ${place} :: ${e}`);
return {
[Symbol.asyncIterator]: async function*() {
let p;
while(p != conditional) {  p = await promiseFn().catch(trace("PromiseUntil[Symbol.asyncIterator]")) }
yield p;
},
resolve: async function(cb = i => i, err = trace("PromiseUntil.resolve")) {
this.resolve = false;
try {
for await (let p of this) {
this.resolved = true;
return cb(p);
}
} catch (e) {
err(e);
}
}
}
}
let a = () => new Promise(res => (Math.random() >= 0.5) ? (console.log("no"), res("not awaited value")) : (console.log("yes"), res("awaited value")));
let waiter = PromiseUntil(a, "awaited value");
waiter.resolve(console.log);


针对条件函数进行了调整:

在考虑了上述答案之后,最好是可以传入条件函数,而不仅仅是要匹配的原始值。这样,您可以检查对象属性或您想要的任何比较等。

此调整将参数名称从conditional更改为conditionalFn,并将 while 循环从:

while(p != conditional)

自:

while(p == undefined || !conditionalFn(p))

例:

在下面,我们有两个构造的PromiseUntil对象。

  • 第一个被分配了一个 promise 函数,当返回的string包含"fin"时,该函数将解析。
  • 第二个被分配了一个 promise 函数,当返回的object(在本例中为用户对象(具有id属性1

function PromiseUntil(promiseFn, conditionalFn) {
let trace = (place) => e => console.log(`trace: ${place} :: ${e}`);
return {
[Symbol.asyncIterator]: async function*() {
let p;
while(p == undefined || !conditionalFn(p)) {  
p = await promiseFn().catch(trace("PromiseUntil[Symbol.asyncIterator]")) 
}
yield p;
},
resolve: async function(cb = i => i, err = trace("PromiseUntil.resolve")) {
this.resolve = false;
try {
for await (let p of this) {
this.resolved = true;
return cb(p);
}
} catch (e) {
err(e);
}
}
}
}
let a = () => new Promise(res => (Math.random() >= 0.5) ? (console.log("First Example: no"), res("not awaited value")) : (console.log("First Example: yes"), res("First Example: finished"))),
b = () => new Promise(res => (Math.random() >= 0.5) ? (console.log("Second Example: no"), res({ id: 2 })) : (console.log("Second Example: yes"), res({id: 1, name: "John Smith" })) );
let waiter = PromiseUntil(a, val => val.includes("fin"));
waiter.resolve(console.log);
let waiter_two = PromiseUntil(b, user => user.id == 1);
waiter_two.resolve(user => console.log(`Second Example: Found User: ${user.id} , ${user.name}`));


旁白:无限循环

在上面的代码中,Promisetill 对象将继续无限期地尝试解析其 Promise 函数。如果从未满足条件,则可能导致无限递归。若要避免这种情况,可以设置尝试和解决该条件的最大持续时间,也可以设置尝试次数。下面演示了如何执行此操作:

function PromiseUntil( promiseFn, conditionalFn, {maxDuration, maxAttempts} = {} ) {
let trace = ( place ) => e => console.log( `trace: ${place} :: ${e}` ),
timeout = ( reason ) => trace( "PromiseUntil Time Out" )( Error( reason ) ),
limit = max => current => ( max && max <= current );
return {
promiseName: promiseFn.name,
[ Symbol.asyncIterator ]: async function*() {
const exceeds = {
duration: limit( Date.now() + maxDuration ),
attempts: limit( maxAttempts )
};
let attempts = 0;
let p;
while ( p == undefined || !conditionalFn( p ) ) {
if ( exceeds.duration( Date.now() ) ) throw timeout( `${this.promiseName} Max Duration Met` );
if ( exceeds.attempts( attempts++ ) ) throw timeout( `${this.promiseName} Max Resolution Attempts` );
p = await promiseFn().catch( trace( "PromiseUntil[Symbol.asyncIterator]" ) );
}
yield p;
},
resolve: async function( cb = i => i, err = trace( "PromiseUntil.resolve" ) ) {
this.resolve = false;
try {
for await ( let p of this ) {
this.resolved = true;
return cb( p );
}
}
catch ( e ) {
if ( e ) err( e );
}
}
}
}
// our promise functions
let delayedPromise = () => new Promise( res =>
setTimeout( () => Math.random() >= 0.5 ? res( "not awaited value" ) : res( "delayedPromise Successful: finished" ), 500 ) ),
	
attemptPromise = () => new Promise( res => 
									 Math.random() >= 0.5 ? res( {id: 2} ) : res( {id: 1, name: "John Smith" } ) );
// our PromiseUntil Objects
let wait_for_fin = PromiseUntil( delayedPromise, val => val.includes( "fin" ), {
maxDuration: 300
} ),
	
wait_for_user_one = PromiseUntil( attemptPromise, user => user.id == 1, {
maxAttempts: 2
} );
// our Resolvers
wait_for_fin.resolve( console.log );
wait_for_user_one.resolve( user => console.log( `attemptPromise Successful: Found User: ${user.id} , ${user.name}` ) );

最新更新