The difference between multitasking and cooperative multitasking is that you can yield the CPU in the middle of a long process. You can do that in Javascript but it involves combining multiple asynchrony APIs in complex ways. Ways you probably don’t want to invite your team to use frequently.
You cannot split a large calculation in the middle by chaining promises to allow even other promises to make progress, let alone event loop processing.
> The difference between multitasking and cooperative multitasking is that you can yield the CPU in the middle of a long process.
As long as "can yield" means "able to yield, and able to not yield". More clearly put, the difference between preemptable and cooperative multitasking is
I'm not sure what you trying to demonstrate here. It's not the problem I'm talking about. For starters, you have no calculation. You're just running a couple awaits and setTimeouts. Of course those are going to run in 1,3,2,4 order.
The question is how would you make sure 4 happens before 2?
Here's a real world example, from Node: You need to make 3 service calls, A B & C, to build a page. Service A is the fastest call, but takes a lot of processing time. Service C is the slowest call, but requires a bit of data from Service B. Since A and B are unrelated, odds are good they're being invoked from completely separate parts of the code.
If you fire A and B, service C won't get called until service A's processing is complete. You could await both A and B, then call C before you start the processing, but you have to turn your code flow inside out to do that, so it only works for trivial applications.
Adding promise chaining to A.process() won't get B's promise to resolve before A's chain finishes resolving. setImmediate() might work in some places, and you might be able to come up with a code pattern that works for your team, but I don't believe it's guaranteed to work everywhere.
My initial point what that your terms are muddled.
Your hypothetical could benefit from a preemptable (aka non-cooperative) scheduler, which can forcibly interrupt A, to allow C to start.
A cooperative scheduler (which is what JS has) is at the mercy of A to properly yield.
---
As for how to yield on the macro- or microtask queue of your choice, they are the same difficulty to write.
// HTML5, Node.js
await new Promise(resolve => resolve());
await new Promise(resolve => setTimeout(resolve, 0));
// Node.js
await new Promise(resolve => resolve());
await new Promise(setImmediate);
You're correct that setTimeout and setImmediate are not guaranteed to work on all ES runtimes, because they are HTML5 and Node.js specific additions. (As is the entire concept of a separate macrotask queue, which you dislike so much.)
You cannot split a large calculation in the middle by chaining promises to allow even other promises to make progress, let alone event loop processing.