Я внимательно прочитал документы, однако есть некоторые части, которые не совсем ясны, и большинство из них связано с актерами / службами.
Я немного неясен в деталях появившихся актеров. жизненные циклы.
- Будут ли звонить
.stop()
на службу с порожденными актерами, также .stop()
порожденными актерами или их оставят в покое? - Как мне убирать порожденных актеров от машина? Есть ли способ доступа к порожденным актерам в
children
изнутри самой службы? (как внутри действия)
Скажем, у меня есть машина с действием для добавления файлов. Когда файл добавляется, для него вызывается новый spawn()
, и ссылка сохраняется в context
. Теперь, когда машина завершает свою работу, я хочу сбросить context
и очистить children
после .stop()
каждого из них.
Чтобы быть полностью определенным c здесь - это то, как я смоделировал систему загрузки, которая реализует поведение, описанное выше.
В этой реализации всякий раз, когда машина возвращается к idle
состояние, я сбрасываю context
и вручную .stop()
каждый порожденный актер. Однако службы актеров все еще находятся в .children
, и я не могу получить к ним доступ изнутри действий машины (добавление 3-го параметра meta
к resetContext
не приводит ни к чему с доступом к текущему children
).
Относительно .stop()
актеров и очистки children
и сброса context
, в идеале я хотел бы иметь отдельное действие для каждого из них, которое будет выполняться при входе в состояние idle
, однако поскольку мне кажется, что я не могу найти способ доступа к children
, единственное решение, о котором я могу подумать, - это сделать все за одно действие через context
.
Также следует отметить, что в официальный пример при удалении задачи ее созданный актер не является .stop()
ed, что заставляет меня задуматься, если это недосмотр или есть причина?
Ниже для вашего удобства приведен код, который реализует система загрузки и файл. Полная реализация, которая также включает в себя визуализатор, может быть найдена здесь :
// upload system machine
const uploadSystemMachine = Machine(
{
id: 'upload-system',
initial: 'idle',
context: {
files: [],
successes: 0,
failures: 0,
cancellations: 0,
},
states: {
idle: {
entry: ['clearSpawnedActors', 'resetContext'],
on: {
OPEN: 'active',
},
},
active: {
initial: 'waitingForFiles',
states: {
waitingForFiles: {
on: {
CLOSE: {
target: '#upload-system.idle',
},
ADD: {
actions: 'addFile',
},
UPLOAD: {
target: 'pending',
cond: 'hasFiles',
},
},
},
pending: {
entry: 'startFileUploads',
on: {
'FILE.UPLOADED': {
actions: 'incrementSuccesses',
},
'FILE.FAILED': {
actions: 'incrementFailures',
},
'FILE.CANCELLED': {
actions: 'incrementCancellations',
},
CANCEL: {
actions: 'cancelFileUpload',
},
'': {
target: 'completed',
cond: 'allSettled',
},
},
},
completed: {
type: 'final',
on: {
CLOSE: '#upload-system.idle',
},
},
},
},
},
on: {
KILL: '.idle',
},
},
{
guards: {
hasFiles: context => {
return context.files.length > 0;
},
hasNoFiles: context => {
return context.files.length === 0;
},
allSettled: context => {
return (
context.files.length === context.successes + context.failures + context.cancellations
);
},
},
actions: {
startFileUploads: context => {
context.files.forEach(actor => {
actor.send('START');
});
},
addFile: assign({
files: (context, event) => {
const newValue = spawn(
fileMachine.withContext(event.target ? event.target.data : {}),
context.files.length
);
return [...context.files, newValue];
},
}),
resetContext: assign({
files: context => {
context.files.forEach(actor => {
console.log(actor);
actor.stop();
});
return [];
},
successes: 0,
failures: 0,
cancellations: 0,
}),
cancelFileUpload: (context, event) => {
const fileID = event.data.id;
for (let i = 0; i < context.files.length; i++) {
if (context.files[i].id === fileID) {
context.files[i].send('CANCEL');
break;
}
}
},
incrementSuccesses: assign({
successes: context => {
return context.successes + 1;
},
}),
incrementFailures: assign({
failures: context => {
return context.failures + 1;
},
}),
incrementCancellations: assign({
cancellations: context => {
return context.cancellations + 1;
},
}),
},
}
);
// file machine
const fileMachine = Machine(
{
id: 'file',
initial: 'idle',
context: null,
states: {
idle: {
on: {
START: 'pending',
},
},
pending: {
invoke: {
id: 'upload',
src: 'upload',
},
on: {
RESOLVED: 'success',
REJECTED: 'failed',
CANCEL: 'cancelled',
},
},
success: {
type: 'final',
entry: [
() => {
console.log('%centering file success state', 'color: green');
},
sendParent('FILE.UPLOADED'),
],
},
failed: {
type: 'final',
entry: [
() => {
console.log('%centering file failed state', 'color: red');
},
sendParent('FILE.FAILED'),
],
},
cancelled: {
type: 'final',
entry: [
() => {
console.log('%centering file cancelled state', 'color: orange');
},
sendParent('FILE.CANCELLED'),
],
},
},
},
{
services: {
upload: (__context, event) => {
return callback => {
let cancelRequest;
let settled;
const req = new Promise((resolve, reject) => {
cancelRequest = reject;
console.log('Started uploading', event);
return setTimeout(() => {
const cond = Math.random() > 0.5;
if (cond) {
resolve();
} else {
reject();
}
}, Math.random() * 5000);
});
req
.then(() => {
settled = true;
console.log('%cFile uploaded successfully', 'color: green');
callback('RESOLVED');
})
.catch(() => {
settled = true;
console.log('%cFile failed to upload', 'color: red');
callback('REJECTED');
});
return () => {
if (!settled) {
console.log('canceling request');
cancelRequest();
}
};
};
},
},
}
);