1

我发现我的许多 API 调用函数在开始时和完成后都会更改loading属性。问题是我有很多功能,我喜欢让我的代码保持干燥。truefalse

所以,我想出了这样的事情:

async loadingWrap (func, ...args) {
  this.loading = true

  await func(...args)

  this.loading = false
}

当我打电话时是这样的:

await this.loadingWrap(
  this.someAsyncFunction, { param: 'Value' }
)

理想情况下我想要的是:

await this.loadingWrap(this.someAsyncFunction({ param: 'Value'}))

所以对于未来的读者(我或其他人)来说,它看起来像是一个正常的功能。

那可能吗?我查看了高阶函数,但到目前为止还没有运气。

4

5 回答 5

1

从你想要的:

await this.loadingWrap(this.someAsyncFunction({ param: 'Value'}))

这不起作用,因为它将参数视为嵌套函数。操作顺序将是:

  1. 称呼this.someAsyncFunction({ param: 'Value'})
  2. call this.loadingWrap(x)wherex是第1步的返回值

这种类型的函数求值与数学函数完全一样,在哪里求值f(g(x))( f of g , given x),首先在给定 value 的情况下对g求值x,然后使用结果对f求值。

一个可能的解决方案...

您也许可以使用 JavaScript 的Proxy对象。正如文档所说,您可以通过使用apply陷阱在函数上使用它们。

您将编写处理程序以处理任何尝试使用加载标志的函数。

const handler = {
  apply: async function(target, thisArg, argumentsList) {
    thisArg.loading = true
    await target.apply(thisArg, argumentsList)
    thisArg.loading = false
  }
}

然后,您将someAsyncFunction通过像这样创建代理来创建您的成员函数:

YourClass.prototype.someAsyncFunction = new Proxy(someAsyncFunction, handler);

然后你这样称呼它:

// inside some other async member function...
  await this.someAsyncFunction({ param: 'Value'})

这是一个可运行的示例(页面上没有任何内容,只有控制台输出):

class MyObj {
  constructor() {
    this.loading = false
  }

  async someAsyncFunction(val) {
    console.log(`entering someAsyncFunction: loading = ${this.loading}`)
    console.log(`calling this.asyncLoad...`)
    await this.asyncLoad({
      value: val
    })
    console.log(`exiting someAsyncFunction: loading = ${this.loading}`)
  }
}

async function asyncLoad(params) {
  return new Promise(resolve => {
    console.log(`entering asyncLoad: loading = ${this.loading}, value = ${params.value}`)
    setTimeout(() => {
      console.log(`exiting asyncLoad: loading = ${this.loading}, value = ${params.value}`)
      resolve()
    }, 1000)
  })
}

const handler = {
  apply: async function(target, thisArg, argumentsList) {
    console.log('PROXY: setting load to true...')
    thisArg.loading = true
    console.log('PROXY: calling the proxied function...')
    await target.apply(thisArg, argumentsList)
    console.log('PROXY: setting load to false...')
    thisArg.loading = false
  }
}

MyObj.prototype.asyncLoad = new Proxy(asyncLoad, handler);

async function run() {
  let myobj = new MyObj()
  console.log(`in run, before calling someAsyncFunction, loading = ${myobj.loading}`)
  setTimeout(() => {
    console.log(`INTERRUPT: checking loading is true (${myobj.loading})`)
  }, 500)
  await myobj.someAsyncFunction(1)
  console.log(`in run, after calling someAsyncFunction, loading = ${myobj.loading}`)
}
run()

选择性代理

如果您尝试调用的函数足够通用,以至于您有时只需要执行代理操作,那么这是完全可行的。这也是Proxy非常酷的地方,因为您可以创建不同的代理来执行不同的操作,同时保持相同的基本代码。

在下面的示例中,asyncLoad是我的通用函数,我可以call将它提供一个实例ObjWithoutStatus作为函数的this上下文。但我也创建了两个代理,一个用于设置loading状态,另一个用于设置loaderIsRunning状态。这些中的每一个最终都会调用基本函数,而不必执行创建维护正确范围的包装器的体操。

class ObjWithoutStatus {
  constructor() {}
}

class ObjWithLoading {
  constructor() {
    this.loading = false
  }
}

class ObjWithLoaderIsRunning {
  constructor() {
    this.loaderIsRunning = false
  }
}

async function asyncLoad(params) {
  return new Promise(resolve => {
    console.log(`entering asyncLoad: loading = ${this.loading}, value = ${params.value}`)
    setTimeout(() => {
      console.log(`exiting asyncLoad: loading = ${this.loading}, value = ${params.value}`)
      resolve()
    }, 1000)
  })
}

const handler_loading = {
  apply: async function(target, thisArg, argumentsList) {
    console.log('PROXY_loading: setting load to true...')
    thisArg.loading = true
    console.log('PROXY_loading: calling the proxied function...')
    await target.apply(thisArg, argumentsList)
    console.log('PROXY_loading: setting load to false...')
    thisArg.loading = false
  }
}

const handler_loaderIsRunning = {
  apply: async function(target, thisArg, argumentsList) {
    console.log('PROXY_loaderIsRunning: setting load to true...')
    thisArg.loaderIsRunning = true
    console.log('PROXY_loaderIsRunning: calling the proxied function...')
    await target.apply(thisArg, argumentsList)
    console.log('PROXY_loaderIsRunning: setting load to false...')
    thisArg.loaderIsRunning = false
  }
}

const asyncLoad_loading = new Proxy(asyncLoad, handler_loading)
const asyncLoad_loaderIsRunning = new Proxy(asyncLoad, handler_loaderIsRunning)

const x = new ObjWithoutStatus()
const y = new ObjWithLoading()
const z = new ObjWithLoaderIsRunning()

async function run() {

  console.log(`in run, before calling asyncLoad, x.loading, x.loaderIsRunning = ${x.loading}, ${x.loaderIsRunning}`)
  setTimeout(() => console.log(`INTERRUPT_asyncLoad: x.loading, x.loaderIsRunning = ${x.loading}, ${x.loaderIsRunning}`), 500)
  await asyncLoad.call(x, {
    value: 1
  })
  console.log(`in run, after calling asyncLoad, x.loading, x.loaderIsRunning = ${x.loading}, ${x.loaderIsRunning}`)

  console.log(`in run, before calling asyncLoad_loading, y.loading = ${y.loading}`)
  setTimeout(() => console.log(`INTERRUPT_asyncLoad_loading: y.loading = ${y.loading}`), 500)
  await asyncLoad_loading.call(y, {
    value: 2
  })
  console.log(`in run, after calling asyncLoad_loading, y.loading = ${y.loading}`)

  console.log(`in run, before calling asyncLoad_loaderIsRunning, z.loaderIsRunning = ${z.loaderIsRunning}`)
  setTimeout(() => console.log(`INTERRUPT_asyncLoad_loading: z.loaderIsRunning = ${z.loaderIsRunning}`), 500)
  await asyncLoad_loaderIsRunning.call(z, {
    value: 3
  })
  console.log(`in run, after calling asyncLoad_loaderIsRunning, z.loaderIsRunning = ${z.loaderIsRunning}`)

}
run()

于 2020-05-06T17:50:00.003 回答
1

你几乎可以得到你想要的。您需要做的是不传递参数并在没有任何参数的情况下调用函数。这与接受回调之类的本机函数的行为类似setTimeout()or addEventListener()

async loadingWrap (func) {
  this.loading = true

  await func()

  this.loading = false
}

然后调用它类似于调用函数的方式,例如setTimeout()

await this.loadingWrap(() => this.someAsyncFunction({ param: 'Value'}))

诀窍是将你的函数包装在一个不接受任何参数的匿名函数中——就像 js 世界中的其他函数一样。

console.log这是替换loading变量的完整工作演示:

async function loadingWrap (func) {
  console.log('loading');
  
  await func()
    
  console.log('done loading');
}

function timer (x) {
  return new Promise((ok,fail) => setTimeout(ok,x));
}

async function test () {
  console.log('calling async function');

  await loadingWrap(() => timer(2000));

  console.log('finished calling async function');
}

test();

于 2020-05-06T17:50:07.367 回答
0

这是可能的,但您需要确保在this调用实际函数时正确设置该值:

async loadingWrap (func, thisArg, ...args) {
  this.loading = true;
  // support functions that resolve to something useful. And provide `this`
  let result = await func.apply(thisArg, args);
  this.loading = false
  return result; 
}

并以某种async方式:

  let result = await this.loadingWrap(
    this.someAsyncFunction, this, { param: 'Value' }
  );
  console.log(result);

如果你不喜欢额外的参数,那么你必须传递一个this正确设置绑定的回调函数,然后你不妨同时解决参数:

async loadingWrap (func) {
  this.loading = true;
  let result = await func();
  this.loading = false
  return result; 
}

在某些async方法中,请注意回调函数:

  let result = await this.loadingWrap(
    () => this.someAsyncFunction({ param: 'Value' })
  );
  console.log(result);
于 2020-05-06T17:42:57.277 回答
0

您正在寻找一个更高阶的函数,它只是一个返回函数的函数。Lodash 将这样的技术用于类似throttleor的函数debounce

// Wrap your function in another function that sets the loading property.
// We're passing "this" as "that" to preserve it when loadStuff is called.
function loadingWrap(that, functionToWrap) {
    return async function() {
        that.loading = true;
        let returnVal = await functionToWrap.apply(that, arguments);
        that.loading = false;
        return returnVal;
    }
}

// In your API class
public loadStuff1 = loadingWrap(this, (arg1, arg2) => {
    // This method must return a promise for the loading wrap to work.
    return http.get('someURL', arg1, arg2);
});

// In the class that uses your api
myAPI.loadStuff1('abc', 123);
于 2020-05-06T18:02:58.380 回答
-1

考虑使用这样的包装器:

function bar(fn) {
  console.log('before');
  fn();
  console.log('after');
}

function baz(...params) {
  console.log('inside', params);
}

bar(() => baz(1, 2, 3));

class A {
  constructor() {
    this.loading = false;
  }

  async loadable(fn) {
      this.loading = true;
      await fn();
      this.loading = false;
  }

  async load() {
    return new Promise(res => setTimeout(res, 2000))
  }

  async fetch() {
    this.loadable(this.load); // or () => this.load(params)
  }
}

new A().fetch();

于 2020-05-06T17:46:53.807 回答