代码之家  ›  专栏  ›  技术社区  ›  Zenoo

使用异步方法保持对象可链接

  •  4
  • Zenoo  · 技术社区  · 7 年前

    假设我有课 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.
    3 回复  |  直到 7 年前
        1
  •  2
  •   t.niese    7 年前

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

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

    一个代理用于包装原始类 Test 因此,可以对每个is实例进行修补,使它们成为可链接的。

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

        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
  •  2
  •   NanoPish    7 年前

    我认为现在不可能使用这种语法。它需要在 在里面 返回它的函数。

    链接功能的不同方式:

    答应那时

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

    或者使用async/await

    for (const f of [func1, func2]) {
      await f();
    }
    
        3
  •  2
  •   Fissure King    7 年前

    正如OP评论中所讨论的,这可以通过使用 Proxy .

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

    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