【问题标题】:Fluent async api with ES6 proxy javascript流利的异步 api 与 ES6 代理 javascript
【发布时间】:2017-10-22 08:17:26
【问题描述】:

所以...我有一些方法。每个方法都返回一个承诺。

     myAsyncMethods: {
          myNavigate () {
            // Imagine this is returning a webdriverio promise
            return new Promise(function(resolve){ 
              setTimeout(resolve, 1000);
            })
          },
          myClick () {
            // Imagine this is returning a webdriverio promise
            return new Promise(function(resolve){
              setTimeout(resolve, 2000);
            })
          }
        }

我正在尝试进行end to end 测试,因此prom 链必须是线性的(第一次单击、下一次导航等)

现在,我可以做到这一点......

makeItFluent(myAsyncMethods)
    .myNavigate()
    .myClick()
    .then(() => myAsyncMethods.otherMethod())
    .then(() => /*do other stuff*/ )

...带有 ES6 代理功能:

function makeItFluent (actions) {
    let prom = Promise.resolve();
    const builder = new Proxy(actions, {
        get (target, propKey) {
            const origMethod = target[propKey];

            return function continueBuilding (...args) {
                // keep chaining promises
                prom = prom.then(() => (typeof origMethod === 'function') && origMethod(...args));

                // return an augmented promise with proxied object
                return Object.assign(prom, builder);
            };
        }
    });

    return builder;
};

但是,我不能做的事情是:

makeItFluent(myAsyncMethods)
    .myNavigate()
    .myClick()
    .then(() => myAsyncMethods.otherMethod())
    .then(() => /*do other stuff*/ )
    .myNavigate()

因为then 不是代理方法,因此它不会返回myAsyncMethods。我尝试代理then,但没有结果。

有什么想法吗?

感谢开发者;)

【问题讨论】:

  • 你真的不应该代理then来装饰无论如何返回一个不承诺。如果每个人都这样做,对你的方法的引用就会随着每个 promise 结果泄露出去。
  • 你的 makeItFluent 东西不允许分支,它总是建立一个线性的 prom 链。避免命令式赋值。
  • Object.assign 确实会破坏您的代理。
  • 谢谢@Bergi,我知道这不是最好的方法。但我必须弄清楚这一点。
  • 我在makeItFluent 中看不到分支的需要,你能解释一下吗?我只需要返回myAsyncMethods 的代理,对吗? :s 每当调用一个方法时,它都会返回一个带有代理方法的承诺,因为当我执行这些东西时,我可以看到 getter 内部的痕迹。你让我感到困惑,因为它有效......也许我看不出你的意思

标签: javascript es6-promise fluent es6-proxy chainable


【解决方案1】:

https://michaelzanggl.com/articles/end-of-chain/

promise 只不过是一个符合规范的“thenable”(具有 then() 方法的对象)。而 await 只是对 Promise 的封装,以提供更简洁、更简洁的语法。

class NiceClass {
  promises = [];

  doOne = () => {
    this.promises.push(new Promise((resolve, reject) => {
        this.one = 1;
        resolve();
    }));
    return this;
  }

  doTwo = () => {
    this.promises.push(new Promise((resolve, reject) => {
      this.two = 2;
  
      resolve();
    }));

    return this;
  }

  async then(resolve, reject) {
    let results = await Promise.all(this.promises);
    resolve(results);
  }

  build = () => {
    return Promise.all(this.promises)
  }
}

你可以用两种方式调用它们。

(async () => {
  try {
    let nice = new NiceClass();
    let result = await nice
      .doOne()
      .doTwo();

    console.log(nice);

    let nice2 = new NiceClass();
    let result2 = await nice2
      .doOne()
      .doTwo()
      .build();

    console.log(nice2, result2);
  } catch(error) {
    console.log('Promise error', error);
  }
})();

【讨论】:

    【解决方案2】:

    我会从 yourAsyncMethods 返回包装好的 Promises,它允许将同步和异步方法与 Proxy 和 Reflect 混合并以正确的顺序执行它们:

    /* WRAP PROMISE */
    let handlers;
    const wrap = function (target) {
        if (typeof target === 'object' && target && typeof target.then === 'function') {
            // The target needs to be stored internally as a function, so that it can use
            // the `apply` and `construct` handlers.
            var targetFunc = function () { return target; };
            targetFunc._promise_chain_cache = Object.create(null);
            return new Proxy(targetFunc, handlers);
        }
        return target;
    };
    // original was written in TS > 2.5, you might need a polyfill :
    if (typeof Reflect === 'undefined') {
        require('harmony-reflect');
    }
    handlers = {
        get: function (target, property) {
            if (property === 'inspect') {
                return function () { return '[chainable Promise]'; };
            }
            if (property === '_raw') {
                return target();
            }
            if (typeof property === 'symbol') {
                return target()[property];
            }
            // If the Promise itself has the property ('then', 'catch', etc.), return the
            // property itself, bound to the target.
            // However, wrap the result of calling this function.
            // This allows wrappedPromise.then(something) to also be wrapped.
            if (property in target()) {
                const isFn = typeof target()[property] === 'function';
                if (property !== 'constructor' && !property.startsWith('_') && isFn) {
                    return function () {
                        return wrap(target()[property].apply(target(), arguments));
                    };
                }
                return target()[property];
            }
            // If the property has a value in the cache, use that value.
            if (Object.prototype.hasOwnProperty.call(target._promise_chain_cache, property)) {
                return target._promise_chain_cache[property];
            }
            // If the Promise library allows synchronous inspection (bluebird, etc.),
            // ensure that properties of resolved
            // Promises are also resolved immediately.
            const isValueFn = typeof target().value === 'function';
            if (target().isFulfilled && target().isFulfilled() && isValueFn) {
                return wrap(target().constructor.resolve(target().value()[property]));
            }
            // Otherwise, return a promise for that property.
            // Store it in the cache so that subsequent references to that property
            // will return the same promise.
            target._promise_chain_cache[property] = wrap(target().then(function (result) {
                if (result && (typeof result === 'object' || typeof result === 'function')) {
                    return wrap(result[property]);
                }
                const _p = `"${property}" of "${result}".`;
                throw new TypeError(`Promise chain rejection: Cannot read property ${_p}`);
            }));
            return target._promise_chain_cache[property];
        },
        apply: function (target, thisArg, args) {
            // If the wrapped Promise is called, return a Promise that calls the result
            return wrap(target().constructor.all([target(), thisArg]).then(function (results) {
                if (typeof results[0] === 'function') {
                    return wrap(Reflect.apply(results[0], results[1], args));
                }
                throw new TypeError(`Promise chain rejection: Attempted to call ${results[0]}` +
                    ' which is not a function.');
            }));
        },
        construct: function (target, args) {
            return wrap(target().then(function (result) {
                return wrap(Reflect.construct(result, args));
            }));
        }
    };
    // Make sure all other references to the proxied object refer to the promise itself,
    // not the function wrapping it
    Object.getOwnPropertyNames(Reflect).forEach(function (handler) {
        handlers[handler] = handlers[handler] || function (target, arg1, arg2, arg3) {
            return Reflect[handler](target(), arg1, arg2, arg3);
        };
    });
    

    您可以将它与您的方法一起使用,例如

    myAsyncMethods: {
          myNavigate () {
            // Imagine this is returning a webdriverio promise
            var myPromise = new Promise(function(resolve){ 
              setTimeout(resolve, 1000);
            });
            return wrap(myPromise)
          },
    // ...
    

    注意两件事:

    您现在可以像这样混合它

    FOO.myNavigate().mySyncPropertyOrGetter.myClick().mySyncMethod().myNavigate() ...
    

    【讨论】:

    • btw:正在开发一个涉及 WeakMap 的版本以避免内存泄漏——所有这些技术都是“未来的 JS”,所以它是非常新的......
    • 不错!谢谢你的时间,我会试一试然后回来;)
    • 不用担心。我也需要这件作品;)如果有问题请告诉我……
    猜你喜欢
    • 1970-01-01
    • 2023-03-11
    • 2015-08-08
    • 1970-01-01
    • 2018-12-06
    • 2015-12-30
    • 2016-09-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多