# 调用服务

¥Invoking Services

这些 XState v4 文档不再维护

XState v5 现已推出!阅读有关 XState v5 的更多信息 (opens new window)

¥XState v5 is out now! Read more about XState v5 (opens new window)

🆕 我们的 关于 XState 中参与者的部分 (opens new window) 有关于 promise、回调、可观察、操作和参与者的解释和示例。

¥🆕 Our section on actors in XState (opens new window) has explainers and examples for promises, callbacks, observables, actions, and actors.

🚀快速参考

¥🚀 Quick Reference

在一台机器上表达整个应用的行为很快就会变得复杂且笨重。使用相互通信的多台机器来表达复杂的逻辑是很自然的(并且受到鼓励!)。这与 角色模型 (opens new window) 非常相似,其中每个机器实例都被视为一个 "actor",可以向其他 "actors"(例如 Promise 或其他机器)发送和接收事件(消息),并对它们做出反应。

¥Expressing the entire app's behavior in a single machine can quickly become complex and unwieldy. It is natural (and encouraged!) to use multiple machines that communicate with each other to express complex logic instead. This closely resembles the Actor model (opens new window), where each machine instance is considered an "actor" that can send and receive events (messages) to and from other "actors" (such as Promises or other machines) and react to them.

为了使机器之间能够相互通信,父机器调用子机器并通过 sendParent(...) 监听从子机器发送的事件,或者等待子机器到达其 最终状态,这将导致进行 onDone 转换。

¥For machines to communicate with each other, the parent machine invokes a child machine and listens to events sent from the child machine via sendParent(...), or waits for the child machine to reach its final state, which will then cause the onDone transition to be taken.

你可以调用:

¥You can invoke:

  • Promise,将在 resolve 上进行 onDone 转换,或在 reject 上进行 onError 转换

    ¥Promises, which will take the onDone transition on resolve, or the onError transition on reject

  • 回调,可以向父机发送事件和从父机接收事件

    ¥Callbacks, which can send events to and receive events from the parent machine

  • 观测值,可以向父机发送事件,以及完成时发出信号

    ¥Observables, which can send events to the parent machine, as well as a signal when it is completed

  • 机器,也可以发送/接收事件,并且在到达其 最终状态 时也通知父机

    ¥Machines, which can also send/receive events, and also notify the parent machine when it reaches its final state

# invoke 属性

¥The invoke Property

调用是在状态节点的配置中使用 invoke 属性定义的,其值是一个包含以下内容的对象:

¥An invocation is defined in a state node's configuration with the invoke property, whose value is an object that contains:

  • src - 要调用的服务的来源,可以是:

    ¥src - the source of the service to invoke, which can be:

    • 机器

      ¥a machine

    • 返回 Promise 的函数

      ¥a function that returns a Promise

    • 返回 "回调处理程序" 的函数

      ¥a function that returns a "callback handler"

    • 返回可观察值的函数

      ¥a function that returns an observable

    • 一个字符串,它指的是原生的 options.services 中定义的 4 个列出的选项中的任何一个

      ¥a string, which refers to any of the 4 listed options defined in this machine's options.services

    • 调用源对象 4.12,其中包含 { type: src } 中的源字符串以及任何其他元数据。

      ¥an invoke source object 4.12, which contains the source string in { type: src }, as well as any other metadata.

  • id - 被调用服务的唯一标识符

    ¥id - the unique identifier for the invoked service

  • onDone - (可选)在以下情况下采取的 transition

    ¥onDone - (optional) the transition to be taken when:

    • 子机达到 最终状态,或者

      ¥the child machine reaches its final state, or

    • 调用的 Promise 解决了,或者

      ¥the invoked promise resolves, or

    • 被调用的可观察对象完成

      ¥the invoked observable completes

  • onError - (可选)当调用的服务遇到执行错误时要进行的转换。

    ¥onError - (optional) the transition to be taken when the invoked service encounters an execution error.

  • autoForward - (可选)true 如果发送到此计算机的所有事件也应发送(或转发)到调用的子级(默认情况下为 false

    ¥autoForward - (optional) true if all events sent to this machine should also be sent (or forwarded) to the invoked child (false by default)

    • ⚠️ 避免将 autoForward 设置为 true,因为盲目转发所有事件可能会导致意外行为和/或无限循环。始终更喜欢显式发送事件,和/或使用 forwardTo(...) 操作创建器将事件直接转发到服务。

      ¥⚠️ Avoid setting autoForward to true, as blindly forwarding all events may lead to unexpected behavior and/or infinite loops. Always prefer to explicitly send events, and/or use the forwardTo(...) action creator to directly forward an event to a service.

  • data - (可选,仅在调用机器时使用)将子机器的 context 的属性映射到从父机器的 context 返回相应值的函数的对象。

    ¥data - (optional, used only when invoking machines) an object that maps properties of the child machine's context to a function that returns the corresponding value from the parent machine's context.

警告

不要将某个状态的 onDone 属性与 invoke.onDone 混淆 - 它们是相似的过渡,但指的是不同的事物。

¥Don't get the onDone property on a state confused with invoke.onDone - they are similar transitions, but refer to different things.

  • 状态节点 上的 onDone 属性是指到达 最终状态 的复合状态节点。

    ¥The onDone property on a state node refers to the compound state node reaching a final state.

  • invoke.onDone 属性指的是正在完成的调用 (invoke.src)。

    ¥The invoke.onDone property refers to the invocation (invoke.src) being done.





 







 



// ...
loading: {
  invoke: {
    src: someSrc,
    onDone: {/* ... */} // refers to `someSrc` being done
  },
  initial: 'loadFoo',
  states: {
    loadFoo: {/* ... */},
    loadBar: {/* ... */},
    loadingComplete: { type: 'final' }
  },
  onDone: 'loaded' // refers to 'loading.loadingComplete' being reached
}
// ...

# 调用 Promise

¥Invoking Promises

由于每个 Promise 都可以建模为状态机,因此 XState 可以按原样调用 Promise。Promise 可以:

¥Since every promise can be modeled as a state machine, XState can invoke promises as-is. Promises can either:

  • resolve(),将进行 onDone 转场

    ¥resolve(), which will take the onDone transition

  • reject()(或抛出错误),这将进行 onError 转换

    ¥reject() (or throw an error), which will take the onError transition

如果在 Promise 结算之前退出被调用 Promise 处于活动状态的状态,则 Promise 的结果将被丢弃。

¥If the state where the invoked promise is active is exited before the promise settles, the result of the promise is discarded.

// Function that returns a promise
// This promise might resolve with, e.g.,
// { name: 'David', location: 'Florida' }
const fetchUser = (userId) =>
  fetch(`url/to/user/${userId}`).then((response) => response.json());

const userMachine = createMachine({
  id: 'user',
  initial: 'idle',
  context: {
    userId: 42,
    user: undefined,
    error: undefined
  },
  states: {
    idle: {
      on: {
        FETCH: { target: 'loading' }
      }
    },
    loading: {
      invoke: {
        id: 'getUser',
        src: (context, event) => fetchUser(context.userId),
        onDone: {
          target: 'success',
          actions: assign({ user: (context, event) => event.data })
        },
        onError: {
          target: 'failure',
          actions: assign({ error: (context, event) => event.data })
        }
      }
    },
    success: {},
    failure: {
      on: {
        RETRY: { target: 'loading' }
      }
    }
  }
});

解析后的数据被放入 'xstate.done.actor.<id>' 事件中的 data 属性下,例如:

¥The resolved data is placed into a 'xstate.done.actor.<id>' event, under the data property, e.g.:

{
  type: 'xstate.done.actor.getUser',
  data: {
    name: 'David',
    location: 'Florida'
  }
}

# Promise 拒绝

¥Promise Rejection

如果 Promise 被拒绝,则 onError 转换将通过 { type: 'xstate.error.actor' } 事件进行。错误数据可在事件的 data 属性上获取:

¥If a Promise rejects, the onError transition will be taken with a { type: 'xstate.error.actor' } event. The error data is available on the event's data property:

const search = (context, event) =>
  new Promise((resolve, reject) => {
    if (!event.query.length) {
      return reject('No query specified');
      // or:
      // throw new Error('No query specified');
    }

    return resolve(getSearchResults(event.query));
  });

// ...
const searchMachine = createMachine({
  id: 'search',
  initial: 'idle',
  context: {
    results: undefined,
    errorMessage: undefined
  },
  states: {
    idle: {
      on: {
        SEARCH: { target: 'searching' }
      }
    },
    searching: {
      invoke: {
        id: 'search',
        src: search,
        onError: {
          target: 'failure',
          actions: assign({
            errorMessage: (context, event) => {
              // event is:
              // { type: 'xstate.error.actor', data: 'No query specified' }
              return event.data;
            }
          })
        },
        onDone: {
          target: 'success',
          actions: assign({ results: (_, event) => event.data })
        }
      }
    },
    success: {},
    failure: {}
  }
});

警告

如果缺少 onError 转换并且 Promise 被拒绝,则错误将被忽略,除非你为机器指定了 严格模式。在这种情况下,严格模式将停止机器并抛出错误。

¥If the onError transition is missing and the Promise is rejected, the error will be ignored unless you have specified strict mode for the machine. Strict mode will stop the machine and throw an error in this case.

# 调用回调

¥Invoking Callbacks

发送到父计算机的事件流可以通过回调处理程序进行建模,该处理程序是一个接受两个参数的函数:

¥Streams of events sent to the parent machine can be modeled via a callback handler, which is a function that takes in two arguments:

(可选)返回值应该是一个函数,该函数在退出当前状态时对调用的服务执行清理(即取消订阅、防止内存泄漏等)。回调不能使用 async/await 语法,因为它会自动将返回值封装在 Promise 中。

¥The (optional) return value should be a function that performs cleanup (i.e., unsubscribing, preventing memory leaks, etc.) on the invoked service when the current state is exited. Callbacks cannot use async/await syntax because it automatically wraps the return value in a Promise.

// ...
counting: {
  invoke: {
    id: 'incInterval',
    src: (context, event) => (callback, onReceive) => {
      // This will send the 'INC' event to the parent every second
      const id = setInterval(() => callback('INC'), 1000);

      // Perform cleanup
      return () => clearInterval(id);
    }
  },
  on: {
    INC: { actions: assign({ counter: context => context.counter + 1 }) }
  }
}
// ...

# 监听父事件

¥Listening to Parent Events

调用的回调处理程序还提供第二个参数 onReceive,它注册从父级发送到回调处理程序的事件的监听器。这允许父计算机和调用的回调服务之间进行父子通信。

¥Invoked callback handlers are also given a second argument, onReceive, which registers listeners for events sent to the callback handler from the parent. This allows for parent-child communication between the parent machine and the invoked callback service.

例如,父机器向子 'ponger' 服务发送 'PING' 事件。子服务可以使用 onReceive(listener) 监听该事件,并将 'PONG' 事件发送回父服务作为响应:

¥For example, the parent machine sends the child 'ponger' service a 'PING' event. The child service can listen for that event using onReceive(listener), and send a 'PONG' event back to the parent in response:

const pingPongMachine = createMachine({
  id: 'pinger',
  initial: 'active',
  states: {
    active: {
      invoke: {
        id: 'ponger',
        src: (context, event) => (callback, onReceive) => {
          // Whenever parent sends 'PING',
          // send parent 'PONG' event
          onReceive((e) => {
            if (e.type === 'PING') {
              callback('PONG');
            }
          });
        }
      },
      entry: send({ type: 'PING' }, { to: 'ponger' }),
      on: {
        PONG: { target: 'done' }
      }
    },
    done: {
      type: 'final'
    }
  }
});
const actor = interpret(pingPongMachine);
actor.subscribe({ complete: () => done() });
actor.start();

# 调用 Observables 4.6

¥Invoking Observables 4.6

观测值 (opens new window) 是随时间推移发出的值流。将它们视为一个数组/集合,其值是异步发出的,而不是一次性发出的。JavaScript 中有许多可观察量的实现;最受欢迎的是 RxJS (opens new window)

¥Observables (opens new window) are streams of values emitted over time. Think of them as an array/collection whose values are emitted asynchronously, instead of all at once. There are many implementations of observables in JavaScript; the most popular one is RxJS (opens new window).

可以调用 Observables,预计将事件(字符串或对象)发送到父机器,但不接收事件(单向)。可观察调用是一个以 contextevent 作为参数并返回可观察事件流的函数。当退出被调用的状态时,可观察对象将被取消订阅。

¥Observables can be invoked, which is expected to send events (strings or objects) to the parent machine, yet not receive events (uni-directional). An observable invocation is a function that takes context and event as arguments and returns an observable stream of events. The observable is unsubscribed when the state where it is invoked is exited.

import { createMachine, interpret } from 'xstate';
import { interval } from 'rxjs';
import { map, take } from 'rxjs/operators';

const intervalMachine = createMachine({
  id: 'interval',
  initial: 'counting',
  context: { myInterval: 1000 },
  states: {
    counting: {
      invoke: {
        src: (context, event) =>
          interval(context.myInterval).pipe(
            map((value) => ({ type: 'COUNT', value })),
            take(5)
          ),
        onDone: 'finished'
      },
      on: {
        COUNT: { actions: 'notifyCount' },
        CANCEL: { target: 'finished' }
      }
    },
    finished: {
      type: 'final'
    }
  }
});

上面的 intervalMachine 将接收来自 interval(...) 映射到事件对象的事件,直到可观察到 "completed"(完成触发值)。如果 "CANCEL" 事件发生,则 observable 将被释放(.unsubscribe() 将在内部调用)。

¥The above intervalMachine will receive the events from interval(...) mapped to event objects, until the observable is "completed" (done emitting values). If the "CANCEL" event happens, the observable will be disposed (.unsubscribe() will be called internally).

提示

不一定需要为每次调用创建可观察对象。可以改为引用 "热可观察":

¥Observables don't necessarily need to be created for every invocation. A "hot observable" can be referenced instead:

import { fromEvent } from 'rxjs';

const mouseMove$ = fromEvent(document.body, 'mousemove');

const mouseMachine = createMachine({
  id: 'mouse',
  // ...
  invoke: {
    src: (context, event) => mouseMove$
  },
  on: {
    mousemove: {
      /* ... */
    }
  }
});

# 调用机器

¥Invoking Machines

机器分层通信,被调用的机器可以通信:

¥Machines communicate hierarchically, and invoked machines can communicate:

  • 通过 send(EVENT, { to: 'someChildId' }) 行动进行亲子互动

    ¥Parent-to-child via the send(EVENT, { to: 'someChildId' }) action

  • 通过 sendParent(EVENT) 操作从子级到父级。

    ¥Child-to-parent via the sendParent(EVENT) action.

如果退出机器调用的状态,则机器停止。

¥If the state where the machine is invoked is exited, the machine is stopped.

import { createMachine, interpret, send, sendParent } from 'xstate';

// Invoked child machine
const minuteMachine = createMachine({
  id: 'timer',
  initial: 'active',
  states: {
    active: {
      after: {
        60000: { target: 'finished' }
      }
    },
    finished: { type: 'final' }
  }
});

const parentMachine = createMachine({
  id: 'parent',
  initial: 'pending',
  states: {
    pending: {
      invoke: {
        src: minuteMachine,
        // The onDone transition will be taken when the
        // minuteMachine has reached its top-level final state.
        onDone: 'timesUp'
      }
    },
    timesUp: {
      type: 'final'
    }
  }
});

const service = interpret(parentMachine)
  .onTransition((state) => console.log(state.value))
  .start();
// => 'pending'
// ... after 1 minute
// => 'timesUp'

# 使用上下文调用

¥Invoking with Context

可以使用 context 调用子计算机,该 context 派生自具有 data 属性的父计算机的 context。例如,下面的 parentMachine 将调用初始上下文为 { duration: 3000 } 的新 timerMachine 服务:

¥Child machines can be invoked with context that is derived from the parent machine's context with the data property. For example, the parentMachine below will invoke a new timerMachine service with initial context of { duration: 3000 }:

const timerMachine = createMachine({
  id: 'timer',
  context: {
    duration: 1000 // default duration
  }
  /* ... */
});

const parentMachine = createMachine({
  id: 'parent',
  initial: 'active',
  context: {
    customDuration: 3000
  },
  states: {
    active: {
      invoke: {
        id: 'timer',
        src: timerMachine,
        // Deriving child context from parent context
        data: {
          duration: (context, event) => context.customDuration
        }
      }
    }
  }
});

就像 assign(...) 一样,子上下文可以映射为对象(首选)或函数:

¥Just like assign(...), child context can be mapped as an object (preferred) or a function:

// Object (per-property):
data: {
  duration: (context, event) => context.customDuration,
  foo: (context, event) => event.value,
  bar: 'static value'
}

// Function (aggregate), equivalent to above:
data: (context, event) => ({
  duration: context.customDuration,
  foo: event.value,
  bar: 'static value'
})

警告

data 替换机器上定义的默认 context;它没有被合并。这种行为将在下一个主要版本中改变。

¥The data replaces the default context defined on the machine; it is not merged. This behavior will change in the next major version.

# 完成数据

¥Done Data

当子机到达其顶层 最终状态 时,它可以在 "done" 事件中发送数据(例如 { type: 'xstate.done.actor.someId', data: ... })。此 "完成数据" 在最终状态的 data 属性上指定:

¥When a child machine reaches its top-level final state, it can send data in the "done" event (e.g., { type: 'xstate.done.actor.someId', data: ... }). This "done data" is specified on the final state's data property:

const secretMachine = createMachine({
  id: 'secret',
  initial: 'wait',
  context: {
    secret: '42'
  },
  states: {
    wait: {
      after: {
        1000: { target: 'reveal' }
      }
    },
    reveal: {
      type: 'final',
      data: (context, event) => ({
        secret: context.secret
      })
    }
  }
});

const parentMachine = createMachine({
  id: 'parent',
  initial: 'pending',
  context: {
    revealedSecret: undefined
  },
  states: {
    pending: {
      invoke: {
        id: 'secret',
        src: secretMachine,
        onDone: {
          target: 'success',
          actions: assign({
            revealedSecret: (context, event) => {
              // event is:
              // { type: 'xstate.done.actor.secret', data: { secret: '42' } }
              return event.data.secret;
            }
          })
        }
      }
    },
    success: {
      type: 'final'
    }
  }
});

const service = interpret(parentMachine)
  .onTransition((state) => console.log(state.context))
  .start();
// => { revealedSecret: undefined }
// ...
// => { revealedSecret: '42' }

# 发送事件

¥Sending Events

  • 要从子计算机发送到父计算机,请使用 sendParent(event)(采用与 send(...) 相同的参数)

    ¥To send from a child machine to a parent machine, use sendParent(event) (takes the same arguments as send(...))

  • 要从父计算机发送到子计算机,请使用 send(event, { to: <child ID> })

    ¥To send from a parent machine to a child machine, use send(event, { to: <child ID> })

警告

send(...)sendParent(...) 操作创建者并不强制将事件发送到机器。它们是纯函数,返回描述要发送内容的操作对象,例如 { type: 'xstate.send', event: ... }interpreter 将读取这些对象然后发送它们。

¥The send(...) and sendParent(...) action creators do not imperatively send events to machines. They are pure functions that return an action object describing what is to be sent, e.g., { type: 'xstate.send', event: ... }. An interpreter will read these objects and then send them.

了解有关 send 的更多信息

¥Read more about send

以下是两台机器 pingMachinepongMachine 相互通信的示例:

¥Here is an example of two machines, pingMachine and pongMachine, communicating with each other:

import { createMachine, interpret, send, sendParent } from 'xstate';

// Parent machine
const pingMachine = createMachine({
  id: 'ping',
  initial: 'active',
  states: {
    active: {
      invoke: {
        id: 'pong',
        src: pongMachine
      },
      // Sends 'PING' event to child machine with ID 'pong'
      entry: send({ type: 'PING' }, { to: 'pong' }),
      on: {
        PONG: {
          actions: send({ type: 'PING' }, { to: 'pong', delay: 1000 })
        }
      }
    }
  }
});

// Invoked child machine
const pongMachine = createMachine({
  id: 'pong',
  initial: 'active',
  states: {
    active: {
      on: {
        PING: {
          // Sends 'PONG' event to parent machine
          actions: sendParent(
            { type: 'PONG' },
            {
              delay: 1000
            }
          )
        }
      }
    }
  }
});

const service = interpret(pingMachine).start();

// => 'ping'
// ...
// => 'pong'
// ..
// => 'ping'
// ...
// => 'pong'
// ...

# 发送响应 4.7+

¥Sending Responses 4.7+

被调用的服务(或 衍生角色)可以响应另一个服务/参与者;即,它可以发送事件来响应另一个服务/参与者发送的事件。这是通过 respond(...) 动作创建器完成的。

¥An invoked service (or spawned actor) can respond to another service/actor; i.e., it can send an event in response to an event sent by another service/actor. This is done with the respond(...) action creator.

例如,下面的 'client' 机器将 'CODE' 事件发送到调用的 'auth-server' 服务,然后该服务在 1 秒后响应 'TOKEN' 事件。

¥For example, the 'client' machine below sends the 'CODE' event to the invoked 'auth-server' service, which then responds with a 'TOKEN' event after 1 second.

import { createMachine, send, actions } from 'xstate';

const { respond } = actions;

const authServerMachine = createMachine({
  id: 'server',
  initial: 'waitingForCode',
  states: {
    waitingForCode: {
      on: {
        CODE: {
          actions: respond('TOKEN', { delay: 1000 })
        }
      }
    }
  }
});

const authClientMachine = createMachine({
  id: 'client',
  initial: 'idle',
  states: {
    idle: {
      on: {
        AUTH: { target: 'authorizing' }
      }
    },
    authorizing: {
      invoke: {
        id: 'auth-server',
        src: authServerMachine
      },
      entry: send({ type: 'CODE' }, { to: 'auth-server' }),
      on: {
        TOKEN: { target: 'authorized' }
      }
    },
    authorized: {
      type: 'final'
    }
  }
});

这个具体例子可以使用 sendParent(...) 来达到同样的副作用;不同之处在于 respond(...) 会将事件发送回接收到的事件的来源,该来源可能不一定是父计算机。

¥This specific example can use sendParent(...) for the same effect; the difference is that respond(...) will send an event back to the received event's origin, which might not necessarily be the parent machine.

# 多项服务

¥Multiple Services

你可以通过在数组中指定每个服务来调用多个服务:

¥You can invoke multiple services by specifying each in an array:

// ...
invoke: [
  { id: 'service1', src: 'someService' },
  { id: 'service2', src: 'someService' },
  { id: 'logService', src: 'logService' }
],
// ...

每次调用都会创建该服务的一个新实例,因此即使多个服务的 src 相同(例如上面的 'someService'),也会调用 'someService' 的多个实例。

¥Each invocation will create a new instance of that service, so even if the src of multiple services are the same (e.g., 'someService' above), multiple instances of 'someService' will be invoked.

# 配置服务

¥Configuring Services

调用源(服务)的配置方式与操作、防护等的配置方式类似 - 通过将 src 指定为字符串并在机器选项的 services 属性中定义它们:

¥The invocation sources (services) can be configured similar to how actions, guards, etc. are configured -- by specifying the src as a string and defining them in the services property of the Machine options:

const fetchUser = // (same as the above example)

const userMachine = createMachine(
  {
    id: 'user',
    // ...
    states: {
      // ...
      loading: {
        invoke: {
          src: 'getUser',
          // ...
        }
      },
      // ...
    }
  },
  {
  services: {
    getUser: (context, event) => fetchUser(context.user.id)
  }
);

调用 src 还可以指定为对象 4.12,该对象描述调用源及其 type 和其他相关元数据。这可以从 meta.src 参数中的 services 选项中读取:

¥The invoke src can also be specified as an object 4.12 that describes the invoke source with its type and other related metadata. This can be read from the services option in the meta.src argument:

const machine = createMachine(
  {
    initial: 'searching',
    states: {
      searching: {
        invoke: {
          src: {
            type: 'search',
            endpoint: 'example.com'
          }
          // ...
        }
        // ...
      }
    }
  },
  {
    services: {
      search: (context, event, { src }) => {
        console.log(src);
        // => { endpoint: 'example.com' }
      }
    }
  }
);

# 测试

¥Testing

通过将服务指定为上面的字符串,可以通过使用 .withConfig() 指定替代实现来完成 "mocking" 服务:

¥By specifying services as strings above, "mocking" services can be done by specifying an alternative implementation with .withConfig():

import { interpret } from 'xstate';
import { assert } from 'chai';
import { userMachine } from '../path/to/userMachine';

const mockFetchUser = async (userId) => {
  // Mock however you want, but ensure that the same
  // behavior and response format is used
  return { name: 'Test', location: 'Anywhere' };
};

const testUserMachine = userMachine.withConfig({
  services: {
    getUser: (context, event) => mockFetchUser(context.id)
  }
});

describe('userMachine', () => {
  it('should go to the "success" state when a user is found', (done) => {
    interpret(testUserMachine)
      .onTransition((state) => {
        if (state.matches('success')) {
          assert.deepEqual(state.context.user, {
            name: 'Test',
            location: 'Anywhere'
          });

          done();
        }
      })
      .start();
  });
});

# 参考服务 4.7+

¥Referencing Services 4.7+

服务(以及 actors,它们是衍生服务)可以直接从 .children 属性在 状态对象 上引用。state.children 对象是服务 ID(键)到这些服务实例(值)的映射:

¥Services (and actors, which are spawned services) can be referenced directly on the state object from the .children property. The state.children object is a mapping of service IDs (keys) to those service instances (values):

const machine = createMachine({
  // ...
  invoke: [
    { id: 'notifier', src: createNotifier },
    { id: 'logger', src: createLogger }
  ]
  // ...
});

const service = interpret(machine)
  .onTransition((state) => {
    state.children.notifier; // service from createNotifier()
    state.children.logger; // service from createLogger()
  })
  .start();

当 JSON 序列化时,state.children 对象是服务 ID(键)到包含有关该服务的元数据的对象的映射。

¥When JSON serialized, the state.children object is a mapping of service IDs (keys) to objects containing meta data about that service.

# 快速参考

¥Quick Reference

invoke 属性

¥The invoke property

const machine = createMachine({
  // ...
  states: {
    someState: {
      invoke: {
        // The `src` property can be:
        // - a string
        // - a machine
        // - a function that returns...
        src: (context, event) => {
          // - a promise
          // - a callback handler
          // - an observable
        },
        id: 'some-id',
        // (optional) forward machine events to invoked service (currently for machines only!)
        autoForward: true,
        // (optional) the transition when the invoked promise/observable/machine is done
        onDone: { target: /* ... */ },
        // (optional) the transition when an error from the invoked service occurs
        onError: { target: /* ... */ }
      }
    }
  }
});

调用 Promise

¥Invoking Promises

// Function that returns a promise
const getDataFromAPI = () => fetch(/* ... */)
    .then(data => data.json());


// ...
{
  invoke: (context, event) => getDataFromAPI,
  // resolved promise
  onDone: {
    target: 'success',
    // resolved promise data is on event.data property
    actions: (context, event) => console.log(event.data)
  },
  // rejected promise
  onError: {
    target: 'failure',
    // rejected promise data is on event.data property
    actions: (context, event) => console.log(event.data)
  }
}
// ...

调用回调

¥Invoking Callbacks

// ...
{
  invoke: (context, event) => (callback, onReceive) => {
    // Send event back to parent
    callback({ type: 'SOME_EVENT' });

    // Receive events from parent
    onReceive(event => {
      if (event.type === 'DO_SOMETHING') {
        // ...
      }
    });
  },
  // Error from callback
  onError: {
    target: 'failure',
    // Error data is on event.data property
    actions: (context, event) => console.log(event.data)
  }
},
on: {
  SOME_EVENT: { /* ... */ }
}

调用可观察对象

¥Invoking Observables

import { map } from 'rxjs/operators';

// ...
{
  invoke: {
    src: (context, event) => createSomeObservable(/* ... */).pipe(
        map(value => ({ type: 'SOME_EVENT', value }))
      ),
    onDone: 'finished'
  }
},
on: {
  SOME_EVENT: /* ... */
}
// ...

调用机器

¥Invoking Machines

const someMachine = createMachine({ /* ... */ });

// ...
{
  invoke: {
    src: someMachine,
    onDone: {
      target: 'finished',
      actions: (context, event) => {
        // Child machine's done data (.data property of its final state)
        console.log(event.data);
      }
    }
  }
}
// ...