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

How can I implement a repetitive promise call chain?

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

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

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 都是同步的。

简而言之,我怎样才能继续调用同样发生在 return promise 中的函数 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对象。

  • 第一个被分配了一个承诺函数,该函数将在返回的 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}`));


旁白:无限循环

在上面的代码中,PromiseUntil 对象将无限期地继续尝试并解析它们的 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}` ) );