I will say though, I personally get the impression that attempts at "concurrency" in JavaScript (in production code) are quite rare, which I attribute to how difficult it is.
That is to say, I don't know if there really _is_ a "default pattern".
I will say though, I personally get the impression that attempts at "concurrency" in JavaScript (in production code) are quite rare, which I attribute to how difficult it is.
That is to say, I don't know if there really _is_ a "default pattern".
Here:
const results = await Promise.all([task1(), task2()]);
Could you give a side by side comparison (with and without ts-chan) so we can better understand what kind of problem it is attempting to solve?My understanding was that Go channels / CSP solves concurrency in a multithreaded environment where reads/writes need to coordinated, but since JavaScript is single threaded, I'm not sure I understand why they would be useful in JavaScript. In JavaScript concurrent tasks can simply communicate by writing/reading shared variables.
To be clear, `ts-chan` is not intended to target any use case already addressed by promises or async/await.
You mentioned CSP so I'll assume you've got context re: that topic. I believe I understand your point re: synchronisation between threads, which is fair, but I'd point out that race conditions still exist in JavaScript - I'd even say they are common, at least in my experience. It is easiest to maintain the integrity of the internal state of complex data structures when only a single logical process can mutate that state at a time.
Example in a similar vein: Firewall daemon that accepts commands over RPC, and performs system configuration, in a linear, blocking fashion, to avoid blowing things up (say it runs `iptables` and/or `nft` commands, under the hood). It would be trivial to have a select statement, with a channel per command (or just one, perhaps), receiving the input payload. In JS, the response would probably be via callback, rather than a ping-pong channel recv then send, or the like.
It wasn't a firewall daemon (although it did interact with firewalld and more), but that's exactly a pattern I've implemented in Go, for a past employer. I don't imagine anyone is keen to implement such a thing in JavaScript, but it's a pattern that applies to anything that mutates state, especially if that state is fragile or complex.
function createRunExclusive() {
let runningTask = Promise.resolve();
return async (asyncFn) => {
runningTask = runningTask.then(async () => {
return await asyncFn();
});
return runningTask;
}
}
// Example usage:
// The idea is that any command that should not overlap should use the same "runExclusive" function
const runExclusive = createRunExclusive();
function handleIpTablesCommand() {
runExclusive(async () => {
await doSomethingWithIpTables();
})
}
Although it's probably best to just use one of the queue libraries on npm. This one for example: https://www.npmjs.com/package/p-queueI oversimplified my example perhaps - it also involved handling interruptions (certain system events), maintaining a lifecycle (set up and tear down), and scenarios where it allowed a certain subset of operations to be performed, while performing one of several operations. That last requirement was due to it using shell scripts to perform configuration of the system, and it needing to extract runtime and configuration information from the main daemon.
Still though, thanks very much for your comments, I've enjoyed reading them.
I agree and this is exactly what js event loop provides. So I don’t understand ts-chan
To be clear, I'm not saying "don't do any communication by sharing state", just that there are use cases where it's possible to make it much simpler to reason about.
As an example, you might control the state of "making a HTTP request to perform a search", within the frontend of a single page app that has a map, search filters, and results.
One strategy is to use a buffered channel (1 element), and, when the search filters are updated, drain then re-send the request to the channel.
The logic processing these requests would then just need to sit there, iterating on / receiving from the channel. It could also support cancellation, if that was desired.
(I'd imagine the results would be propagated via some other mechanism, e.g. to a store implementation)
I'm not sure what specifically you were imagining, but I've added an example of how "vanilla JS" can achieve fan-in, using an AsyncGenerator: https://github.com/joeycumines/ts-chan/blob/main/docs/patter...
It uses one of the patterns suggested in a comment chain above, which I think is pretty neat, and wasn't one that readily occurred to me: https://news.ycombinator.com/item?id=38163562
I'm not making a case for using ts-chan for any situation where a simple generator-based solution suffices. I wouldn't call the example solution (in my first link) simple, but it's something I'd personally be ok with maintaining. Like, I'd approve a PR containing something similar without significant qualms, _if_ there was a significant enough motivator, and it was sufficiently unit tested. I might suggest `ts-chan` as an alternative, to make it easier to maintain, but wouldn't be particularly concerned either way.
That's all very subjective, though :)
Regarding the default vs better, a comparative example with a real concurrent task coded with/out your library would be my preferred way to understand it clearly.