作用域 (EffectScope)
EffectScope
提供了与插件作用域相关的 API。你可以通过 ctx.scope
获取当前上下文的作用域,也可以通过 ctx.plugin()
创建新的作用域。
状态机
插件运行状态有以下可能值:
名称 | 值 | 描述 |
---|---|---|
PENDING | 0 | 等待依赖 |
LOADING | 1 | 正在启用 |
ACTIVE | 2 | 启用完成 |
FAILED | 3 | 启用或停用失败 |
DISPOSED | 4 | 已被移除 |
UNLOADING | 5 | 正在停用 |
各个状态之间的转换关系如下图所示:
下面是对各个状态及其转换关系的详细说明:
对于正常运行的插件,其状态可能会在
PENDING
和ACTIVE
之间往返。PENDING
是所有插件的初始状态,表示插件无法访问到所需的全部依赖。ACTIVE
表示插件已经顺利启用完成。- 作用域会持续监听所有依赖的变动。一旦所有的依赖都可用 (或者插件没有依赖),插件会经由
LOADING
进入ACTIVE
状态。对于已经处于ACTIVE
状态的插件,如果某些依赖变得不可用,插件会经由UNLOADING
再次进入PENDING
状态。 - 除了依赖变动外,配置更新也可能导致插件的状态在
PENDING
和ACTIVE
之间往返。
插件的
LOADING
和UNLOADING
状态具有惰性。- 如果某个插件处于
LOADING
状态,此时某些依赖变得不可用,则该插件会在启用到下一个 检查点 后再进入UNLOADING
状态;如果在此期间依赖再次可用 (并且跟之前相同),则插件将会在启用完成后正常进入ACTIVE
状态。 - 反之,如果某个插件处于
UNLOADING
状态,此时所有依赖变得可用,则该插件也会等待整个UNLOADING
过程完成后再进入LOADING
状态;如果在此期间依赖再次不可用,则插件将会在停用完成后正常进入PENDING
状态。 - 我们因此将
LOADING
和UNLOADING
称为 惰性状态 (Inert State),其他状态称为非惰性状态。当插件正处于某个方向上的惰性状态时,反方向的状态转移需要等到插件即将进入下一个非惰性状态时才被执行或丢弃。
- 如果某个插件处于
当
LOADING
或UNLOADING
执行过程中发生错误,插件会回收所有残存的副作用,随后进入FAILED
状态。插件无法从此状态恢复到上述正常流程中。当插件被 移除 时,会先进行副作用回收,随后进入
DISPOSED
状态。
实例属性
scope.ctx
- 类型:
Context
插件作用域的根上下文。
scope.config
- 类型:
any
插件配置。
scope.state
- 类型:
ScopeState
插件运行状态。
实例方法
scope.effect(callback, label?) 混入
- callback:
() => Effect
回调函数 - label:
string
标签 - 返回值:
Disposable & PromiseLike<Disposable>
回收副作用
立即执行 callback
并收集其产生的副作用。返回的函数可用于回收副作用。
callback
的返回值类型如下:
type Disposable = () => void | Promise<void>
type Effect =
| Disposable
| Promise<Disposable>
| Iterable<Disposable, void, void>
| AsyncIterable<Disposable, void, void>
当 callback
同步或异步地返回函数时,该函数会被视为副作用回收。当 callback
返回同步或异步的迭代器时,迭代器中的每一个 yield
都应该返回一个函数,视为这一阶段的副作用回收。存在多个副作用回收函数时,返回的函数将会倒序且异步地执行每一个副作用回收函数。
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
函数。
const dispose = scope.effect(callback)
await dispose() // 等待异步 callback 执行完成,然后回收副作用
const dispose = await scope.effect(callback)
// 等待异步 callback 执行完成
await dispose() // 回收副作用
特别地,如果 callback
返回异步迭代器,则调用返回的副作用回收函数时,该迭代器可能尚未迭代完毕。此时迭代器将会等待执行到下一个 yield
处,随后回收到此为止的副作用。
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>
等待当前插件进入非惰性状态 (即 LOADING
或 UNLOADING
以外的状态)。
如果该状态是 FAILED
,返回的 Promise 会被拒绝。
TIP
一种常见的用法是等待某个插件启用完成 (如果失败则抛出错误):
// 这里的 await 会自动调用 .then() 方法
await ctx.plugin(plugin)