Skip to content

异步计算

引言

AutoStore提供了非常强大的异步计算属性特性,使用computed来声明创建一个异步计算属性。

提示

所有computed(async (scope)=>{....})声明的异步计算属性均会被原地替换为AsyncComputedValue对象。

工作内幕

创建异步计算属性的基本方法是直接在State中任意位置使用computed进行声明。

tsx
import { computed } from "@autostorejs/react"
const store = createStore({
  order:{
    price:10,
    count:1,
    total:computed(async (scope)=>{
      return scope.price*scope.count
    },['./price','./count'])
  }
})
  • 以上total是一个异步计算属性,并且手动指定依赖了./price./count(相对路径依赖,见依赖收集)。

重点:

当我们使用createStore创建异步计算属性,内部主要做了两件事:

  • 1. 将声明原地替换为AsyncComputedValue

经过createStore处理后,store.state.order.total的值会被替换为AsyncComputedValue类型的值,即:

json
{   
  "loading":false,
  "timeout":0,
  "retry":0,
  "error":null,
  "value":10,
  "progress":0
}

当异步计算的依赖发生变化时,会自动触发计算属性的重新计算,并更新value以及loadingerrorprogress等状态。详见下文高级特性。

  • 2. 创建AsyncComputedObject对象

同时会创建一个名称为声明所在路径名称AsyncComputedObject对象保存在store.computedObjects中。 因此,在上例中,store.computedObjects.get("order.total")就是AsyncComputedObject对象。

computed

computed是一个普通的函数,用于声明计算属性,异步计算属性的函数签名如下:

ts
function computed<Value = any, Scope = any>(
  getter: AsyncComputedGetter<Value,Scope>,
  depends: ComputedDepends,
  options?: ComputedOptions<Value,Scope>
):ComputedDescriptorBuilder<Value,Scope>;

参数说明:

参数 类型 说明
getter AsyncComputedGetter 异步计算函数
depends ComputedDepends 声明依赖
options ComputedOptions 异步计算属性相关参数

异步计算函数

getter参数(即异步计算函数),其返回值将更新到状态中的computed声明的路径上,详见介绍

指定依赖

  • depends:依赖收集,用来指定依赖的状态路径。如何指定依赖详见依赖收集
  • options:异步计算属性的一些选项,详见选项

配置参数

options参数是一个ComputedOptions对象,用来指定计算属性的一些选项。详见计算选项

基本用法

异步计算属性的创建与同步计算一样均是使用computed来声明,但是最重要的一点是异步计算需要显式指定依赖

  • 以上fullName是一个异步计算属性,手动指定其依赖于user.firstName./lastName(相对路径)。
  • 依赖可以使用绝对路径或相对路径,使用.作为路径分割符,./指的是当前对象,../指的是父对象,详见依赖收集
  • 当在输入框架中修改firstNamelastName时,fullName会自动重新计算。
  • 计算属性的结果保存在state.user.fullName.value中。
  • 当计算属性正在计算时,state.user.fullName.loadingtrue。计算完成后,state.user.fullName.loadingfalse
  • 关于...bind('user.firstName')的用法详见表单绑定

高级特性🔥

加载状态

异步计算属性的加载状态保存在AsyncComputedValue对象的loading属性中。

  • loading=true时,代表异步计算正在进行中。
  • loading=false时,代表异步计算已经完成。

以下是一个异步计算加载状态的例子:

  • useAsyncReactive用来返回异步计算属性的状态数据。

执行进度

异步计算属性允许控制计算的进度,执行进度保存在AsyncComputedValue对象的progress属性中,当progress0-100时,代表异步计算的进度。开发者可以根据进度值来展示进度条等。

使用方法如下:

  • 当调用getProgressbar函数时会启动进度条功能,可以控制进度条的进度。
  • getProgressbar函数返回一个进度条对象,该对象有两个方法:valueendvalue用来设置进度值,end用来结束进度条。

超时处理

在创建computed时可以指定超时参数(单位为ms),实现超时处理倒计时功能。基本过程是这样的。

  1. 指定options.timeout=超时时间
  2. 当异步计算开始时,会启动一个定时器时,并更新AsyncComputedValue对象的timeout属性。
  3. 当超时触发时会触发TIMEOUT错误,将错误更新到AsyncComputedValue.error属性中。

倒计时

超时功能中不会自动更新timeout属性,可以通过timeout=[超时时间,间隔更新时长]来启用倒计时功能。

基本过程如下:

  1. 指定options.timoeut=[超时时间,间隔更新时长]
  2. 当异步计算开始时,会启动一个定时器,更新AsyncComputedValue对象的timeout属性。
  3. 然后每隔间隔更新时长就更新一次AsyncComputedValue.timoeut
  4. 当超时触发时会触发TIMEOUT错误,将错误更新到AsyncComputedValue.error属性中。

例如:options.timoeut=[5*1000,5]代表超时时间为5秒,每1000ms更新一次timeout属性,倒计时5次。

重试

在创建computed时可以指定重试参数,实现出错重试执行的功能。基本过程是这样的。

  • 指定options.retry=[重试次数,重试间隔ms]
  • 当开始执行异步计算前,会更新AsyncComputedValue.retry属性。
  • 当执行出错时,会同步更新AsyncComputedValue.retry属性为重试次数。

说明

  • 重试次数为0时,不会再次重试。重试次数为N时,实际会执行N+1次。
  • 重试期间error会更新为最后一次错误信息。

取消

在创建computed时可以传入一个abortSignal参数,该参数返回一个AbortSignal,用来取消计算操作。

基本操作方法是:

  • computed中传入abortSignal参数,该参数是一个AbortSignal,可用来订阅abort信号或者传递给fetchaxios等。
  • 取消时可以调用AsyncComputedObject.cancel()方法来触发一个AbortSignal信号。如下例中调用state.order.total.cancel()

注意

  • abortSignal参数是一个AbortSignal对象,可以用来订阅abort信号或者传递给fetchaxios等。
  • 需要注意的,如果想让计算函数是可取消的,则当调用AsyncComputedObject.cancel()时,计算函数应该在接收到abortSignal信号时,主动结束退出计算函数。如果计算函数没有订阅abort信号,调用AsyncComputedObject.cancel()是不会生效的。

不可重入

默认情况下,每当依赖发生变化时均会执行异步计算函数,在连续变化时就会重复执行异步计算函数。

在声明时,允许指定options.reentry=false来防止重入,如果重入则只会在控制台显示一个警告。

简写异步计算

大部份情况下,异步计算属性均应该使用computed进行声明,但也可以直接使用一个异步函数。

ts
const order = {
    bookName:"ZhangFisher",
    price:100,
    count:3,
    total:async (order)=>{
      return order.price*order.count
    }
}

上述简单的异步声明方式等效于以下方式:

tsx
import { createStore,computed} from "@autostorejs/react"

const store = createStore({
    bookName:"ZhangFisher",
    price:100,
    count:3,
    total:computed(async (order)=>{
      return order.price*order.count
    },[]) // 依赖是空的
}
 )

当不使用computed进行异步计算属性声明时,需要注意以下几点:

  • 默认scope指向的是current,即total所在的对象。
  • 其依赖是空,所以不会自动收集依赖,也不会自动重新计算。也就是说上例中的pricecount变化时,total不会自动重新计算。但是在会在第一次访问时自动计算一次。
  • 如果需要重新计算,可以手动执行store.state.total.run()store.computedObjects.get(<id>).run()

注意事项

  • 当异步计算函数返回一个Promise时的问题

computed内部使用isAsync来判断传入的getter函数是否是一个异步函数,以采取不同的处理逻辑。 但是在低版本JS场景下,这个判断可能不正确。

比如在进行babel将代码转译到es5等低版本代码时,异步函数可能会被转译为同步函数,此时需要显式指定options.async=true

ts
const store = createStore({
    firstName:"Zhang",
    lastName:"Fisher",
    fullName: computed(async (user)=>{
      return user.firstName+user.lastName
    },["user.firstName","user.lastName"],{
      async:true
    })
  })

显式指定computed(async ()=>{...},[...],{async:true}),这样就可以正确识别为异步函数。