Inversion of Control is a common phenomenon that you come across when extending frameworks. Indeed it's often seen as a defining characteristic of a framework. -- Martin Fowler
The key part is the 'framework' calls you rather than you calling the 'framework'. The 'framework' doesn't need to be any specific thing. An operating system, a language runtime, or an application framework could be that 'framework'. As you register an event handler (no matter explicit .addEventHander or implicit lifecycle), you are already using IoC.
But is IoC always good? Or we should say, is IoC always what we desired?
IMO most of IoC is not intended (DI is the one I think of so far), but the control is by nature inverted, especially in human-computer interaction programming, because this is how the computer works: it is the user controls. If user doesn't input then the program just keeps idling.
# The example provided in the blog above
puts 'What is your name?'
name = gets
process_name(name)
puts 'What is your quest?'
quest = gets
process_quest(quest)
Then here comes a question: why does command line enquiry seem to keep the control? Isn't the thread blocking and waiting for a signal as well? That is true and in fact the computer is in certain degree event-driven, in form of interruptions. However, conceptually it is in control because it's an imperative process: statements executed in order. There is a clear flow of control.
And I think sometimes we need our control back. Like when there are callback hells. And luckily coroutine(async/await) is to rescue.
// in control
async function foo() {
const x = await fetch('http://bar.com/');
//...
}
// control inverted
function foo() {
fetch('http://bar.com/').then(x=>{
//...
})
//...
// we get two flows of control now?
}
Maybe this example is so simple that you don't see any difference :/. So let's see another example: you are going to manage a WebSocket connection and it has some rules:
- when connected, server sends you 'LOGIN' and you should reply 'LOGIN:<auth token>' and finally when server replies 'VERIFIED' the connection is successful.
- whenever server sends 'PING', client must reply 'PONG' (in 30 seconds maybe) otherwise connection will be closed.
Normally WebSocket is event-driven and we might write a version like this
const socket = new WebSocket('foo.bar');
let isVerified = false;
socket.onopen = ()=> {
socket.onmessage = ({data}) => {
if(data=='LOGIN') {
socket.send('LOGIN:'+authToken);
}
else if(data=='VERIFIED') {
isVerified = true;
}
else if(data=='PING') {
socket.send('PONG')
} else {
// process the data
}
}
}
This code might work. But there are edge cases like what if server doesn't reply the expected message? To add more state (isLogined, isSuccessful) is a solution but it's redundant (you might check current state whenever callback executed), and not easily-extendable if there are more steps.
However if the control is inverted, the logic will become much more natural. Imagine we have a IoCWebSocket
and it provides a modified WebSocket
with extra methods:
// resolve when it's open
async ready():Promise<void>;
// resolve when there is a incoming message
// reject when websocket error
async read():Promise<string>;
Then the logic becomes:
const socket = new IoCWebSocket('foo.bar');
// ...
// assume inside an `async` function body
await socket.ready();
let next = await socket.read();
if(next!='LOGIN') {
throw Error('Unexpected reply '+next);
}
socket.send('LOGIN:'+authToken);
next = await socket.read();
if(next!='VERIFIED') {
throw Error('Unexpected reply '+next)
}
while(true) {
try {
next = await socket.read();
if(next=='PING') {
socket.send('PONG')
}
else {
// process the data
}
}
catch {
// may catch error and re-throw if it's not due to connection closed.
break;
}
}
Do you notice the order (which is an implicit rule) is naturally guaranteed?
And we may have many analogous situations like drag-n-drop, step-by-step enquiry, cheat codes, (stateful) animations and interactions(hold to activate/n-times click)......they are procedures with extra (temporary) context informations, they're designed to have an order, they could own their own flow of control.
Bonus: Co-routines as an alternative to state machines
This post is about 'what'. As for 'how', I'm still investigating the most optimal solutions. Promise
and async/await are fine in most cases. Sister post: Promise!==coroutine
Top comments (17)
I contribute to an experimental programming language called _hyperscript, which has what we call async transparency and event driven control flow. Check out the draggable window example, it seems closely related to what you are describing.
That’s very interesting!
The context for this article was our twitter discussion here:
twitter.com/magnemg/status/1418523...
twitter.com/san3shain/status/14189...
On programming languages, you might find my article interesting, which started the discussion in the first place:
Features of a Dream Programming Language
The first Foo method is not in control of when the fetch returns, is not blocking during the fetch, but does not progress to next step until the system performs the await. I'm thinking it is IoC because the system does indeed resume control on to original stack in same way as a event would. I never think of this pattern as IoC rather I call it the async/await pattern.
For me, IoC is using Dependency Injection, Interfaces, and even functions, the idea being the caller is able to drive the behaviors.
……I don't know if you did get my idea…The "control" I'm talking about has nothing to do with thread/blocking/stack, but a mental interpretion of logic flows.
No I understand just don't think it's a good article on IoC, too many problems.
Yeah someday I will find out what's not so correct. I do have some similar experiences for several times, due to have different (levels of) perspectives to a problem.
Issues so far: poor English; ill-demostrated example;
But the good news is this post may be improved. The topic is good for sure.
Also allowing a Ping Pong scheme at the application layer demonstrates a misunderstanding of
the TCP/IP layer. Why? because that's what a time out exception is for.
That's for demonstration purpose……to strictly speak, I will not design a protocol like this (but I did have once integrated a service like this…)
Yes I brought this up because many times network people will tell developers to implement a Ping Pong scheme because "nothing is wrong with the hallowed network". When the real answer is always "the network is unstable, please keep working to fix it"
The most common root cause of this type issue is, a router or switch resets due to signal failures.
Off-topic though. Ping-pong isn't necessarily used for checking network status. E.g. measure the delay, "let me know you are not dead", etc. Maybe it's my fault to use the name ping-pong but not "echo" so someone can nitpick.
The socket send receive cycle is enough to determine latency. However the protocol should be ICMP for best results because the Tcp/Ip retry logic gets in the way. Ping runs at ICMP layer.
What does this mean?
"The key part is 'framework' call you rather you call 'framework'. ".
You need the context from the first link I've provided.
No it's just poor English. It should say:
The key part is the 'framework' calls you rather than you calling the 'framework'
I see. Thanks. I do speak poor English 😅 and I'll keep practicing.
When I first read it, I seriously didn't understand it; but was eventually able to correct it mentally after thinking about it.