Skip to content

作用域 (EffectScope)

EffectScope 提供了与插件作用域相关的 API。你可以通过 ctx.scope 获取当前上下文的作用域,也可以通过 ctx.plugin() 创建新的作用域。

状态机

插件运行状态有以下可能值:

名称描述
PENDING0等待依赖
LOADING1正在启用
ACTIVE2启用完成
FAILED3启用或停用失败
DISPOSED4已被移除
UNLOADING5正在停用

各个状态之间的转换关系如下图所示:

LOADING
PENDING
ACTIVE
FAILED
UNLOADING
DISPOSED

下面是对各个状态及其转换关系的详细说明:

  • 对于正常运行的插件,其状态可能会在 PENDINGACTIVE 之间往返。

    • PENDING 是所有插件的初始状态,表示插件无法访问到所需的全部依赖。
    • ACTIVE 表示插件已经顺利启用完成。
    • 作用域会持续监听所有依赖的变动。一旦所有的依赖都可用 (或者插件没有依赖),插件会经由 LOADING 进入 ACTIVE 状态。对于已经处于 ACTIVE 状态的插件,如果某些依赖变得不可用,插件会经由 UNLOADING 再次进入 PENDING 状态。
    • 除了依赖变动外,配置更新也可能导致插件的状态在 PENDINGACTIVE 之间往返。
  • 插件的 LOADINGUNLOADING 状态具有惰性。

    • 如果某个插件处于 LOADING 状态,此时某些依赖变得不可用,则该插件会在启用到下一个 检查点 后再进入 UNLOADING 状态;如果在此期间依赖再次可用 (并且跟之前相同),则插件将会在启用完成后正常进入 ACTIVE 状态。
    • 反之,如果某个插件处于 UNLOADING 状态,此时所有依赖变得可用,则该插件也会等待整个 UNLOADING 过程完成后再进入 LOADING 状态;如果在此期间依赖再次不可用,则插件将会在停用完成后正常进入 PENDING 状态。
    • 我们因此将 LOADINGUNLOADING 称为 惰性状态 (Inert State),其他状态称为非惰性状态。当插件正处于某个方向上的惰性状态时,反方向的状态转移需要等到插件即将进入下一个非惰性状态时才被执行或丢弃。
  • LOADINGUNLOADING 执行过程中发生错误,插件会回收所有残存的副作用,随后进入 FAILED 状态。插件无法从此状态恢复到上述正常流程中。

  • 当插件被 移除 时,会先进行副作用回收,随后进入 DISPOSED 状态。

实例属性

scope.ctx

插件作用域的根上下文。

scope.config

  • 类型: any

插件配置。

scope.state

插件运行状态。

实例方法

scope.effect(callback, label?) 混入

  • callback: () => Effect 回调函数
  • label: string 标签
  • 返回值: Disposable & PromiseLike<Disposable> 回收副作用

立即执行 callback 并收集其产生的副作用。返回的函数可用于回收副作用。

callback 的返回值类型如下:

ts
type 
Disposable
= () => void |
Promise
<void>
type
Effect
=
|
Disposable
|
Promise
<
Disposable
>
|
Iterable
<
Disposable
, void, void>
|
AsyncIterable
<
Disposable
, void, void>

callback 同步或异步地返回函数时,该函数会被视为副作用回收。当 callback 返回同步或异步的迭代器时,迭代器中的每一个 yield 都应该返回一个函数,视为这一阶段的副作用回收。存在多个副作用回收函数时,返回的函数将会倒序且异步地执行每一个副作用回收函数。

ts
const 
dispose
=
scope
.
effect
(function* () {
yield () =>
console
.
log
('副作用 1')
yield async () =>
console
.
log
('副作用 2')
}) await
dispose
() // 依次输出:副作用 2, 副作用 1

如果 callback 同步地返回函数 (注意不是返回同步的函数) 或返回迭代器,则此方法返回时 callback 已经被完全执行完毕,并且其抛出的错误也会被原样抛出。

如果 callback 异步地返回函数 (注意不是返回异步的函数) 或返回异步迭代器,则此方法返回时 callback 可能尚未被执行完毕,并且其中的错误也不会被抛出。你可以通过 await 这个返回值来等待 callback 的异步执行,它会返回一个新的 Disposable 函数。

ts
const 
dispose
=
scope
.
effect
(
callback
)
await
dispose
() // 等待异步 callback 执行完成,然后回收副作用
ts
const 
dispose
= await
scope
.
effect
(
callback
)
// 等待异步 callback 执行完成 await
dispose
() // 回收副作用

特别地,如果 callback 返回异步迭代器,则调用返回的副作用回收函数时,该迭代器可能尚未迭代完毕。此时迭代器将会等待执行到下一个 yield 处,随后回收到此为止的副作用。

ts
const 
dispose
=
scope
.
effect
(async function* () {
await
sleep
(1000)
yield () =>
console
.
log
('副作用 1')
await
sleep
(1000)
yield () =>
console
.
log
('副作用 2')
await
sleep
(1000)
yield () =>
console
.
log
('副作用 3')
}) await
sleep
(1500) // 此时上述第二个 sleep(1000) 尚未执行完毕
await
dispose
() // 将在 500ms 后依次输出:副作用 2, 副作用 1

scope.update(config)

  • config: any 新的插件配置

更新插件配置。

默认情况下,插件会检测新旧配置是否 deepEqual,如果发生了变化,则会停用并重新启用作用域。可以通过 internal/update 事件来手动控制这一行为。

scope.restart() 实验性

停用并重新启用作用域。

scope.dispose()

  • 返回值: Promise<void>

停用并移除插件。

如果在根作用域上调用此方法,则会回收根作用域上的所有副作用 (相当于回到 new Context() 的状态),但并不会将自身 state 设置为 DISPOSED

scope.then(onFulfilled, onRejected)

  • onFulfilled: () => void 成功时的回调函数
  • onRejected: (reason: any) => void 失败时的回调函数
  • 返回值: Promise<void>

等待当前插件进入非惰性状态 (即 LOADINGUNLOADING 以外的状态)。

如果该状态是 FAILED,返回的 Promise 会被拒绝。

TIP

一种常见的用法是等待某个插件启用完成 (如果失败则抛出错误):

ts
// 这里的 await 会自动调用 .then() 方法
await 
ctx
.
plugin
(
plugin
)