-
Notifications
You must be signed in to change notification settings - Fork 1
UI Dataflow Design
NETCREATE is a networked single-page Javascript webapp. One of the complications of this kind of program is sequencing operations that depend on previous operations to complete; you can't easily write procedural code that does this and it's easy to write crappy code that is difficult to debug.
In the past we've created modules that handle difficult event-like asynchronous sequential operations (e.g. reliable network messaging), and we are going to generalize the mechanism for other common scenarios. UNISYS will take on UISTATE and SYSLOOP features with a more consistent syntax so you don't have to remember so many things. We're also going to make use of new features of ES6 and ES7 Javascript to write more robust and compact code.
There are several patterns that we (will) commonly see in NetCreate:
- Browser Level 2 Event handlers, using
<DOM_Element>.addListener()
- Application-defined Events handled by EventEmitter pattern using
UNISYS.On()
and fired usingUNISYS.Emit()
- Application Lifecycle Events hooked by
UNISYS.Hook()
(supercedesSYSLOOP
module) - Application State change events via
UNISYS.SetState()
andUNISYS.OnStateChange()
(supercedesUISTATE
module) - Network Message handlers defined by
UNISYS.RegisterMessage()
and invoked byUNISYS.Call()
andUNISYS.Broadcast()
. - (TBD) Mode changes events and Setting Sets associated with mode hierarchies
- (TBD) Stream events (includes WebSockets)
For our own modules that implement API functions of an asynchronous nature, we'll be using both Promises and ES7 asynch/await. Promises gives us the ability to chain asynchronous operations, whereas Asynch/Await provides an alternative syntax for using Promises that makes code easier to read.
Here's how our asynchronous code used to work compared to how it works now. Note the then()
that wraps the anonymous function in the second form
// 1. old callback-style return
// note: MOD.GetAsyncData() calls provided callback with result
MOD.GetAsyncData(function(data){
console.log('GetAsyncData returned',data);
});
// 2. new Promise-based return
// note: MOD.GetAsyncData() returns a Promise, which implements then()
MOD.GetAsyncData().then(function(data){
console.log('GetAsyncData returned',data);
});
Promises are a little difficult to grasp at first, but I'll talk about that later. For now, know this: the advantage of Promises is that you can chain then()
one after the other, with each anonymous function returning either a value that is passed to the next then()
:
// 3. chain of Promise-based returns
// note: MOD.GetAsyncData() returns a Promise
MOD.GetAsyncData()
.then(function(data){
// decorate data from GetAsyncData()
data = Object.assign(data,{filename:'hi'});
return data;
})
.then(function(data)){
// GetMoreAsyncData() returns a Promise
let moreData = MOD.GetMoreAsyncData(data.filename);
return moreData; // this is a promise, but magic happens
})
.then(function(data){
// data contains the result of GetMoreAsyncData(), which was
// returned above as 'moreData'
console.log('got moreData',data);
});
At this point, the confusing issue is that there is internal code magic that makes it so the then()
chain functions receive data instead of the previously-returned Promise object. This obscures the logic and requires you to know what a Promise does. It's confusing and stupid, but it's vastly improved with the ES7 Javascript standard's new keywords await
and async
. Now you can write code that looks synchronous and resembles sensible logic. The Promise chain above turns into this (note async in front of function
and await keywords in front of the MOD
calls)
// 4. await/async version of Example 3
// first define an async function
async function ChainOp() {
let data = await MOD.GetAsynchData(); // still returns a Promise
data.Object.assign(data,{filename:'hi'});
moreData = await MOD.GetMoreAsyncData(data.fileName); // still returns a Promise
console.log('got',moreData);
}
// now call it
ChainOp();
This looks much more compact, and the great thing is that program execution halts until the awaited Promise completes. This makes the logic of the operation much easier to follow. Not only that, but you can also use try/catch
blocks to handle errors thrown by the Promises.
There is more to say about how Promises actually work, including error handling and how to convert old-style callbacks into new Promises. A Promise is sort of like a magic asynchronous task queue that implements chains of "success" and "failure" functions that execute one-after-the other. It makes it possible to write asynchronous code by avoiding nested callback "pyramid of doom".
// MOD was created with UNISYS.NewModule('name');
// MOD.Call() returns a Promise
MOD.Call('MESSAGENAME', data).then((data)=###{
// data received back
})
.catch((data)=###{
// error occurs if MESSAGENAME doesn't exist
// or if there are more than one handler
});
MOD.CallCollect('MESSAGENAME',data).then((result)=###{
// result contains the results from ALL
// implementors of this event across the network
// as a list of data objects
});
// a message might be locally handled or
// remotely handled. The message is the address
// with UNISYS, so name them accordingly for
// uniqueness!
// MOD was created with UNISYS.NewModule('name');
// MOD.Call() returns a Promise
MOD.Call('MESSAGENAME', data) // returns a Promise
.then((data)=###{
// do something cool
return data;
})
.then(data)=###{
// data is passed from previous
// do something else cool
});
// MOD was created with UNISYS.NewModule('name');
// MOD.Call() returns a Promise
async function GetSomething() {
let data;
try {
// synchronous
data = await MOD.Call('Event',data);
data = await MOD.Call('AnotherEvent',data);
return data;
} catch (err) {
// err is thrown by MOD.Call's promise rejection
}
}
// get something twice (these are running in parallel)
console.log('data',GetSomething());
console.log('data',GetSomething());
// beware asynchronous side effects due to the way Promises
// run the executor function IMMEDIATELY, so if it depends
// on values set outside the function there may be weirdness
// MOD was created with UNISYS.NewModule('name');
// MOD.Broadcast() returns a Promise
MOD.Broadcast('MESSAGENAME',data); // sends out a message to all listeners
// MOD was created with UNISYS.NewModule('name');
MOD.On('EVENTNAME',(data)=###{
// event handler
});
// note that events do not have "duration", so they don't chain
// the way a promise chain would
// MOD was created with UNISYS.NewModule('name');
MOD.Emit('EVENTNAME',data);
Promises can be used to fire START and STOP events on local objects.
Promises can also queue additional success/fail conditions.
Thinking of EVENTS as something happened and then MESSAGES as the action-oriented aspect
might bethe pattern:
- detect the event
2A. do a message sequence
2B. call "action queues" on modules/objects that support them
An action queue is the animation languages where you can load a series of named actions
that use update cycles in a gameloop. This is a different thing though.
Components register named UISTATE
objects with UNISYS.
.. multiple instances of a type of component should be in a list.
.. user interface "mode" is a UISTATE
property.
Component registers for named UISTATE
updates to redraw UI, calling local SetState()
to redraw.
Component updates self through setState()
, and updates UISTATE
through another SetState()
call.
Module registers for changes to named UISTATE objects
Module changes UISTATE
through SetState()
call
There is GLOBAL APP STATE (data) that can be managed by UNISYS
There is also LIFECYCLE APP STATE that is loaded/changed by entering/exciting lifecycles
Certain operations may be affected by the user interface mode and other settings, so need a way to check
There are SETS OF MODES that can also be registered with UNISYS
LIFECYCLE is a special type of MODE
CHANGES IN MODES can fire HOOKS that change GLOBAL APP STATE and also MODAL APP STATE
You can set MODE HIEARCHY by retrieving the MODALITIES and setting a new LIST that determines order of logic.
Use javascript Set()
and Map()
and WeakMap()
to implement these modalities
We'll use similar enter, update, exit terminology
sets of modes and submodes are a thing too. Some modes may not have all submodes.