【问题标题】:how to chain async methods如何链接异步方法
【发布时间】:2018-04-11 03:46:44
【问题描述】:

我编写的 API 有几个不返回值的异步方法,但仍应按调用顺序执行。我想从最终用户那里抽象出等待的解决方案,以便他们可以链接方法调用并期望每个承诺在前一个被解决后执行,如下所示:

api = new Api();
api.doAsync().doAnotherAsync().doAThirdAsync();

从这些方法中获取值并不重要,重要的是它们按顺序执行。我曾尝试使用链接结构,但它并不可靠。

class Api {
    resolvingMethodChain = false;
    constructor() {
        this._methodChain = {
            next: null,
            promise: Promise.resolve(),
        }
    }

    _chain(p) {
        this._methodChain.next = {
            promise: p,
            next: null,
        };

        // if we are not finished resolving the method chain, just append to the current chain
        if (!this.resolvingMethodChain) this._resolveMethodChain(this._methodChain);

        this._methodChain = this._methodChain.next;
        return this
    }

    async _resolveMethodChain(chain) {
        if (!this.resolvingPromiseChain) {
            this.resolvingPromiseChain = true;
        }

        // base case
        if (chain === null) {
            this.resolvingPromiseChain = false;
            return;
        }

        // resolve the promise in the current chain
        await chain.promise;

        // resolve the next promise in the chain
        this._resolvePromiseChain(c.next);   
    }
}

doAsync 方法都将遵从_chain,就像这样

doAsync() {
    const p = new Promise(// do some async stuff);
    return _chain(p); // returns this and adds the promise to the methodChain
}

我知道我可以这样写

async doAsync() {
    // do async thing
    return this;
}

并像这样使用它

doAsync.then(api => api).then(...)

但是如果可以的话,我想避免从每个then 调用中显式返回this 对象,它看起来不像api.doAsync().doAnotherAsync()... 的同步方式那么干净

【问题讨论】:

  • 如何正常使用promise do_Something().then(...).then(...) 等是一个简单的promise链,每个在前一个解决时执行,当其中的操作(本质上是异步)以一种或另一种方式完成时,每个承诺都会得到解决。
  • 这最终会看起来像这样 doSomething().then(api => api).then(...) 我只是想避免每次都显式返回 promise 在回调中返回的内容。

标签: javascript asynchronous method-chaining


【解决方案1】:

你可以从一个简单的 Promise 包装器开始

const effect = f => x =>
  (f (x), x)
  
const Api = (p = Promise.resolve ()) =>
  ({ foo: () => 
       Api (p.then (effect (x => console.log ('foo', x))))
     
   , bar: (arg) =>
       Api (p.then (effect (x => console.log ('bar', arg))))
     
  })
  
Api().foo().foo().bar(5)
// foo undefined
// foo undefined
// bar 5

我们可以添加其他功能来做更多有用的事情。请注意,因为我们使用的是 Promises,所以我们可以轻松地对同步或异步函数进行排序

const effect = f => x =>
  (f (x), x)
  
const square = x =>
  x * x
  
const Api = (p = Promise.resolve ()) =>
  ({ log: () =>
       Api (p.then (effect (console.log)))
       
   , foo: () => 
       Api (p.then (effect (x => console.log ('foo', x))))
     
   , bar: (arg) =>
       Api (p.then (effect (x => console.log ('bar', arg))))
  
   , then: f =>
       Api (p.then (f))
  })

  
Api().log().then(() => 5).log().then(square).log()
// undefined
// 5
// 25

现在添加您想要的任何功能。这个例子展示了实际上做一些更真实的事情的函数

const effect = f => x =>
  (f (x), x)
  
const DB =
  { 10: { id: 10, name: 'Alice' }
  , 20: { id: 20, name: 'Bob' }
  }
  
const Database =
  { getUser: id =>
      new Promise (r =>
        setTimeout (r, 250, DB[id]))
  }
  
const Api = (p = Promise.resolve ()) =>
  ({ log: () =>
       Api (p.then (effect (console.log)))
       
   , getUser: (id) =>
       Api (p.then (() => Database.getUser (id)))
       
   , displayName: () =>
       Api (p.then (effect (user => console.log (user.name))))
  
  })

  
Api().getUser(10).log().displayName().log()
// { id: 10, name: 'Alice' }
// Alice
// { id: 10, name: 'Alice' }

Api().getUser(10).log().getUser(20).log().displayName()
// { id: 10, name: 'Alice' }
// { id: 20, name: 'Bob' }
// Bob

【讨论】:

    【解决方案2】:

    您不需要某种 API 对象来链接 async 函数。如果它们不接受您在示例代码中指示的输入值,那么纯 Promise-chain 方法几乎是相同的,没有任何样板:

    async function delay (ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms)
      })
    }
    
    async function log (message) {
      console.log(message)
    }
    
    async function delay1s () {
      return delay(1000)
    }
    
    async function logNow () {
      const seconds = Math.round(performance.now() / 1000)
    
      return log(`${seconds}s`)
    }
    
    log('start')
      .then(delay1s)
      .then(logNow)
      .then(delay1s)
      .then(logNow)
      .then(delay1s)
      .then(() => log('stop'))

    如您所见,即使是接受输入的函数也只需要包装在一个匿名箭头函数中,这很简单,而且远非不可读。

    【讨论】:

    • 接受的答案正是我想要完成的,但我给出了一个加分,因为我实际上发现这里描述的实现对设计师来说更明显,对最终用户来说微不足道。跨度>
    猜你喜欢
    • 1970-01-01
    • 2011-03-28
    • 2016-12-26
    • 2021-09-15
    • 2015-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多