Actions
There are two types of actions in Leaf instances. Both are available off the .do
property of the instance:
- setters for value fields: Objects and Maps will automatically generate setters for the properties they have at creation.
- user created methods: the configuration (second) parameter has actions that we can use to update the action in more detail.
Actions can call other actions, as deeply as you need to go.(1) They can return an action, change the leaf, or both. (or neither; they can also bridge into external systems, send http requests, etc.)
const leaf = new Forest(
{
x: 0,
y: 0,
z: 0,
},
{
actions: {
updateAll(leaf, x, y, z) {
leaf.do.setX(x);
leaf.do.setY(y);
leaf.do.setZ(z);
},
magnitude(leaf) {
return Math.sqrt(
leaf.value.x ** 2 + leaf.value.y ** 2 + leaf.value.z ** 2
);
},
normalize(leaf) {
const mag = leaf.do.magnitude();
leaf.do.updateAll(
leaf.value.x / mag,
leaf.value.y / mag,
leaf.value.z / mag
);
}
}
}
);
leaf.subscribe(value => console.log('point value is ', value));
leaf.do.setX(1);
leaf.do.setY(2);
leaf.do.setZ(3);
leaf.do.updateAll(4, 5, 6);
leaf.do.normalize();
/**
point value is { x: 0, y: 0, z: 0 }
point value is { x: 1, y: 0, z: 0 }
point value is { x: 1, y: 2, z: 0 }
point value is { x: 1, y: 2, z: 3 }
point value is { x: 4, y: 5, z: 6 }
point value is { x: 0.4558423058385518, y: 0.5698028822981898, z: 0.6837634587578276 }
*/
Async Code and Actions
Actions are by definition synchronous; as they are implicitly wrapped in a transaction, the transaction is supposed
to journal and contain changes in between the start and end of an actions' function. This is not possible to do
if the action is async, as the delayed event cannot be contained or curated. i.e., actions cannot use async
.
One way to achieve asynchronicity is to create an async function outside the leaf. That function can call any leaf actions and those actions will maintain transactional integrity locally.
Another is to with care use promises inside an action, and have the responses be managed by other actions. This works because while the promise itself "leaks out" of the action, its phases are transactionally locked.
This is done in the third getting started example:
const opts = {
actions: {
update(leaf, status, response) {
leaf.do.setStatus(status);
leaf.do.setResponse(response);
},
onFail(leaf, response) {
leaf.do.update('failure', response.data ? response.data : 'error')
},
onResponse(leaf, postResponse) {
if (postResponse.data && postResponse.data.user) {
leaf.do.update('success', postResponse.data.user)
} else if (postResponse.data && postResponse.data.error) {
leaf.do.update('failure', postResponse.data.error)
} else {
leaf.do.update('failure', 'error')
}
},
submit(leaf) {
if (!leaf.$$.get('$isReady')) {
return
}
leaf.do.setStatus('sending')
axios.post('/api/login', {
username: leaf.value.username.value,
password: leaf.value.password.value
})
.then(leaf.do.onResponse)
.catch(leaf.do.onFail)
}
}
}
while the last action, submit
, has a Promise that leaks out of the sync system, because the actual code
in the promises' handlers are themselves actions, you have a series of synchronous code, happening inside an async context.
(1) This does create the opportunity for circular calling, so take care.