网站搜索

javascript - 使用异步方法保持对象可链式调用


假设我有一个类 Test,大约有 10-20 个方法,所有方法都可以链式调用。

在另一种方法中,我有一些异步工作要做。

let test = new Test();
console.log(test.something()); // Test
console.log(test.asynch()); // undefined since the async code isn't done yet
console.log(test.asynch().something()); // ERROR > My goal is to make this 

既然其他所有方法都是可链式调用的,我觉得如果这个唯一的方法不是的话,对用户来说会很奇怪。

有没有办法让我保持我的类的可链式主题?

我已经想过在这个方法的参数中通过回调函数传递下一个方法,但这并不是真正的链式调用。

test.asynch(() => something())

同样的情况也适用于Promises,这并不是真正的链式调用。

test.asynch().then(() => something())

我想要的结果是

test.asynch().something()

这是一个演示我问题的片段:

class Test {
  /**
   * Executes some async code
   * @returns {Test} The current {@link Test}
   */
  asynch() {
    if (true) { //Condition isn't important
      setTimeout(() => { //Some async stuff
        return this;
      }, 500);
    } else {
      // ...
      return this;
    }
  }

  /**
   * Executes some code
   * @returns {Test} The current {@link Test}
   */
  something() {
    // ...
    return this
  }
}

let test = new Test();
console.log(test.something()); // Test
console.log(test.asynch()); // undefined
console.log(test.asynch().something()); // ERROR > My goal is to make this work.

答案#1

我怀疑做那样的事情是否真的一个好主意。但使用代理可以在原始对象满足某些条件时创建这种行为。我强烈建议不要那样做。

请注意,这段代码是一个概念验证,旨在展示它在某种程度上是可行的,但不考虑边缘情况,并且很可能会破坏某些功能。

使用一个代理来包装原始类Test,以便可以修补它的每个实例使它们可以链式调用。

第二个将修补每个函数调用并创建一个队列,用于这些函数调用,以便它们按顺序被调用。

    class Test {
      /**
       * Executes some async code
       * @returns {Test} The current {@link Test}
       */
      asynch() {
        console.log('asynch')
        return new Promise((resolve, reject) => setTimeout(resolve, 1000))
      }

      /**
       * Executes some code
       * @returns {Test} The current {@link Test}
       */
      something() {
        console.log('something')

        return this
      }
    }


    var TestChainable = new Proxy(Test, {
      construct(target, args) {
        return new Proxy(new target(...args), {

          // a promise used for chaining
          pendingPromise: Promise.resolve(),

          get(target, key, receiver) {
            //  intercept each get on the object
            if (key === 'then' || key === 'catch') {
              // if then/catch is requested, return the chaining promise
              return (...args2) => {
                return this.pendingPromise[key](...args2)
              }
            } else if (target[key] instanceof Function) {
              // otherwise chain with the "chainingPromise" 
              // and call the original function as soon
              // as the previous call finished 
              return (...args2) => {
                this.pendingPromise = this.pendingPromise.then(() => {
                  target[key](...args2)
                })

                console.log('calling ', key)

                // return the proxy so that chaining can continue
                return receiver
              }
            } else {
              // if it is not a function then just return it
              return target[key]
            }
          }
        })
      }
    });

    var t = new TestChainable
    t.asynch()
      .something()
      .asynch()
      .asynch()
      .then(() => {
        console.log('all calles are finished')
      })

答案#2

我认为目前不可能使用这样的语法。这需要在函数中访问该 promise 才能返回它。

链式调用函数的不同方式:

使用 then 的 Promise

bob.bar()
    .then(() => bob.baz())
    .then(() => bob.anotherBaz())
    .then(() => bob.somethingElse());

你也可以使用组合,以获得另一种风格的功能性、可重用的语法来链接异步和同步函数

const applyAsync = (acc,val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
const transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2);
transformData(data);

或者使用 async/await

for (const f of [func1, func2]) {
  await f();
}