【问题标题】:Keep object chainable using async methods使用异步方法保持对象可链接
【发布时间】:2019-01-09 03:33:56
【问题描述】:

假设我有一个类 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()

这是一个演示我的问题的 sn-p:

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.

【问题讨论】:

  • 使用 promises 怎么样? :stackoverflow.com/questions/39028882/…
  • @NanoPish 它仍然打破了链接,test.asynch().something() 会变成test.asynch().then(() => something())
  • 阅读我链接的问题的答案,他使用承诺链接了 3 个函数,似乎解决了您的问题?
  • @Zenoo,不幸的是,您所描述的内容是不可能的。至少,据我所知。然而,它充其量只会为其他阅读它的人产生令人困惑的代码,因为这与惯用语相去甚远。你熟悉使用async/await,我猜?这差不多是我们可以将异步代码视为同步的。
  • @Zenoo 嗯......现在我想我已经抓住了你的好奇心。我想知道await 是否会扩展到链式执行,但似乎不会:jsfiddle.net/7v5y9jck。您也许可以创建一个类,将每个方法调用卸载到队列中并返回实例本身。实际上,您可能会为此使用 Proxy,然后包含一个 then 方法,该方法最终会作为您的最终结果解析。

标签: javascript asynchronous method-chaining


【解决方案1】:

我怀疑做这样的事情真的是个好主意。 但是,如果原始对象满足某些条件,则使用Proxy 将允许创建这样的行为。我强烈建议不要那样做。

请注意,此代码是概念证明,表明它在某种程度上是可能的,但不关心边缘情况,很可能会破坏某些功能。

一个代理用于包装原始类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')
      })

【讨论】:

  • 为什么不建议这样做?您是否有一些示例说明使用此代码可能会破坏什么?最后,您认为这会对性能产生不良影响吗?
  • @Zenoo 由于许多项目和想法,问题出现在非常晚的阶段,这主要是/主要是一种模糊的预感。我现在看到的一个问题是每个新开始的链接都将使用前一个链的最后一个 Promise。因此,如果前一个链失败并且错误没有被捕获,那么新启动的链将立即失败。为了克服这个问题,您需要为每个启动的链创建一个自己的代理,但是您需要确保这些链不会交错。
  • @Zenoo,我相信以后会有更多的问题出现。您至少需要很长时间才能测试许多边缘情况。
【解决方案2】:

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

链接函数的不同方式:

答应那时

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

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

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);

或者使用异步/等待

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

【讨论】:

    【解决方案3】:

    正如 cmets 到 OP 中所讨论的,这可以通过使用 Proxy 来完成。

    我知道 t.niese 几个小时前提供了类似的答案。我的方法有些不同,但它仍然实质上是捕获方法调用、返回接收器并在内部堆叠 thennables。

    class ProxyBase {
    
        constructor () {
    
            // Initialize a base thennable.
            this.promiseChain = Promise.resolve();
    
        }
    
        /**
         * Creates a new instance and returns an object proxying it.
         * 
         * @return {Proxy<ProxyBase>}
         */
        static create () {
    
            return new Proxy(new this(), {
    
                // Trap all property access.
                get: (target, propertyName, receiver) => {
    
                    const value = target[propertyName];
    
                    // If the requested property is a method and not a reserved method...
                    if (typeof value === 'function' && !['then'].includes(propertyName)) {
    
                        // Return a new function wrapping the method call.
                        return function (...args) {
    
                            target.promiseChain = target.promiseChain.then(() => value.apply(target, args));
    
                            // Return the proxy for chaining.
                            return receiver;
    
                        }
    
                    } else if (propertyName === 'then') {
                        return (...args) => target.promiseChain.then(...args);
                    }
    
                    // If the requested property is not a method, simply attempt to return its value.
                    return value;
    
                }
    
            });
    
        }
    
    }
    
    // Sample implementation class. Nonsense lies ahead.
    class Test extends ProxyBase {
    
        constructor () {
            super();
            this.chainValue = 0;
        }
    
        foo () {
            return new Promise(resolve => {
                setTimeout(() => {
                    this.chainValue += 3;
                    resolve();
                }, 500);
            });
        }
    
        bar () {
            this.chainValue += 5;
            return true;
        }
    
        baz () {
            return new Promise(resolve => {
                setTimeout(() => {
                    this.chainValue += 7;
                    resolve();
                }, 100);
            });
        }
    
    }
    
    const test = Test.create();
    
    test.foo().bar().baz().then(() => console.log(test.chainValue)); // 15

    【讨论】:

    • 谢谢你,我不知道我是否会最终使用它,但至少你证明了它是可能的。不过我会接受@t.neise 的回答,因为它是第一个发布的并且它也处理catch
    • 这也是我的建议。干杯。
    猜你喜欢
    • 1970-01-01
    • 2016-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-30
    • 1970-01-01
    • 2011-05-13
    • 1970-01-01
    相关资源
    最近更新 更多