【问题标题】:How can I chain functions asynchronously using JavaScript?如何使用 JavaScript 异步链接函数?
【发布时间】:2021-12-31 10:05:11
【问题描述】:

我被要求创建一个名为 foo 的对象,它可以链接函数 logwait

例如:

foo.log('breakfast').wait(3000).log('lunch').wait(3000).log('dinner');

在这种情况下,它首先打印 breakfast,等待 3 秒,然后打印 lunch,然后在 3 秒后打印 dinner

我尝试了类似的方法,但它不起作用。我错过了什么?

var foo = {
  log: function(text){
    console.log(text);
    return foo;
  },

  wait: function(time) {
    setTimeout(function() {
      return foo;
    }, time);
  }
}

foo.log('breakfast').wait(3000).log('lunch').wait(3000).log('dinner');

【问题讨论】:

  • 我猜问题出在等待函数中 - setTimeout 立即返回并且它什么也不返回,所以调用 log on nothing 会出错。
  • @kiner_shah 实现它的正确方法是什么?我不知道如何让它等待然后再次返回对象。
  • 也许让等待异步函数,然后定义睡眠函数,如这篇文章:stackoverflow.com/a/39914235
  • 看起来有点像这个问题:stackoverflow.com/q/32081949/1447675

标签: javascript asynchronous


【解决方案1】:

使用承诺总是更好。此功能的实现可能是;

class Foo {
  constructor(){
    this.promise = Promise.resolve();
  }
  log(txt){
    this.promise = this.promise.then(_ => console.log(txt))
    return this;
  }
  wait(ms){
    this.promise = this.promise.then(_ => new Promise(v => setTimeout(v,ms)));
    return this;
  }
}
  
  var foo = new Foo();
  foo.log("happy").wait(1000).log("new").wait(1000).log("year");

【讨论】:

  • 哇,这是什么玩意儿?非常感谢,我必须检查一下 Promise 并接受它!先生,向您致敬,新年快乐!
  • 您可能希望使该对象成为thenable,以便可以await foo。还可以考虑使其不可变,以便您可以重用一个对象,而不会在两个执行链之间发生冲突。
  • @Bergi 谢谢。实际上,我试图在问题的上下文中简化连续性模式(事先有一个已解决的承诺)。也许在这里再多介绍一下就有点过头了。但是,我仍然希望将promise 属性作为私有实例字段,例如this.#promise,而不是冻结对象。根据thenability,我这里的方法实际上是我的异步队列(AQ)库的构建块,我相信它会做这些事情。
【解决方案2】:

为了记录,Redu's excellent answer 没有class sugar

See also

const foo = {
  promise: Promise.resolve(),
  log(txt) {
    this.promise.then(_ => console.log(txt));
    return this;
  },
  wait(ms) {
    this.promise = this.promise.then(_ => new Promise(v => setTimeout(v, ms)));
    return this;
  }
};

// OR
const Foo = (defaultMs = 1000) => {
  let promised = Promise.resolve();
  return {
    log(txt) {
      promised.then(_ => console.log(txt));
      return this;
    },
    wait: function(ms) {
      promised = promised.then( _=> 
        new Promise( rs => setTimeout(rs, ms || defaultMs) ) );
      return this;
    }
  };
};

foo.log("Happy").wait(1000).log("new").wait(1000).log("year");
Foo().wait(3000)
  .log(`** From Foo ;)`).log(`Happy`).wait().log("new").wait().log("year");

【讨论】:

    【解决方案3】:

    wait 的调用放在前一个中,并作为最后一项,就像一个递归函数。

    meals=['breakfast','elevenses','lunch','afternoon tea','dinner','supper'];
    c=0;
    wait=t=>{setTimeout(function() {
          if (c<meals.length) document.write(meals[c++],'<br>');wait(500);
        }, t);}
    
    wait(500);

    【讨论】:

      【解决方案4】:

      较短,在Promise 以内(不推荐)。

      Promise.prototype.log = function(txt) {
          return this.then(() => console.log(txt))
      }
      
      Promise.prototype.wait = function(ms) {
          return this.then(() => new Promise(res => setTimeout(res, ms)))
      }
      
      var foo = Promise.resolve()
      foo.log('breakfast').wait(3000).log('lunch').wait(3000).log('dinner')
      

      【讨论】:

      • 非常不推荐:Promise.prototype 现在被其他用户破坏了。
      【解决方案5】:

      你可以在没有承诺的情况下做到这一点:

      const foo = {
          log(text) {
              return {...foo, start: () => {
                  this.start();
                  console.log(text);
              }};
          },
          wait(time) {
              return {...foo, start: () => {
                  setTimeout(this.start, time);
              }};
          },
          start() {}
      };
      foo.log('breakfast').wait(3000).log('lunch').wait(3000).log('dinner').start();
      函数foo.log()foo.wait() 立即返回,返回修改后的foo 克隆。使用{...foo} 进行克隆,但修改了start() 函数,使其调用调用者的this.start(),然后调用新操作。链完成后,您调用start() 开始操作。

      【讨论】:

      • 你正在构建的这个隐式链表很有趣 - 不幸的是 wait 以错误的顺序运行它
      • @Bergi 我同意,这是一个有趣的想法,如果你想看看,我已经在我的回答中回收了它。
      【解决方案6】:

      我受到@James 的解决方案的启发,该解决方案部分错误,因为日志消息的顺序相反,但他没有使用Promises。我仍然认为@Redu 的解决方案应该是公认的解决方案(毕竟如果你可以使用Promises,那就完美了),但我认为这个解决方案也很有趣:

      const foo1 = {
          incrementalTimeout: 0,
          nextActions: [],
          log(text) {
              const textLog = () => { console.log(text); };
              if (this.incrementalTimeout == 0)
                  textLog();
              else
                  this.nextActions.push(textLog);
              return this;
          },
          wait(time) {
              let newObj = {...this, incrementalTimeout: this.incrementalTimeout + time, nextActions: []};
              setTimeout(() => { newObj.nextActions.forEach((action) => action()); } , newObj.incrementalTimeout);
              return newObj;
          }
      }
      foo1.log('breakfast').wait(1000).log('lunch').wait(3000).log('dinner');

      我的想法是我不会立即记录text,而是我push 一个带有console.log 的lambda,该数组将在正确的超时到期后被调用。

      我一个接一个地运行所有logwait 操作,但我会跟踪执行操作之前要等待的秒数。每次调用新的wait 时,incrementalTimeout 都会增加time。为了将属于不同时间范围的nextActions 分开,我每次都返回一个newObj,或多或少像@James 那样。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-02-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-18
        • 1970-01-01
        • 1970-01-01
        • 2019-08-20
        相关资源
        最近更新 更多