How about this: you may have heard that Node.js is single-threaded with an event loop. In the basic web server case, it opens up a port. The OS will notify when a connection occurs, thus creating an event. Parse enough of the request, and it might ask for a file. The process will ask the OS for that file, which takes some non-zero amount of time. Now the process is free to handle the next event. That event could be the OS saying, here's that file; or the start of another request, or something else. And if the file is large enough, you wouldn't send it all at once, but a piece (or chunk) at a time.
The basic idea is there's an event queue of things to do. Maybe it's empty: nothing to do. Something comes in, and do that thing, as long as that thing doesn't involve some indeterminate wait, or is likely/known to take "too long", since that will block everything else.
Because a task like downloading a big file can be interleaved with other tasks, the system is effectively multi-tasking. But as a single thread, there are no issues with concurrency and contention, locks and memory models, etc. There are other potential issues 
Several Node helper functions take other functions
setInterval: periodically add this function to the event queue; repeat unless cancelled
setTimeout: wait a while, then add this function to the event queue
setImmediate: add this function to the end of the event queue. If there are other things waiting, do those first, but get to this new function as soon as possible.
On Arduino, there's not much "built-in" that will trigger an event; you have to go "get it" yourself. To do something similar, here's a WorkItem class; with an array of them acting as a ring buffer, it can impersonate a simple event queue
typedef void(*work_t)(void);
constexpr size_t wqSize = 10;
size_t wqHead, wqLen;
class WorkItem {
unsigned long previousMillis;
unsigned long interval;
bool repeat;
work_t work;
void requeue();
bool doWork() {
if (repeat) {
if (millis() - previousMillis < interval) {
requeue();
return false;
}
}
work();
if (repeat) {
unsigned long now = millis();
do {
previousMillis += interval; // try to maintain cadence
} while (now - previousMillis > interval); // even when falling behind
requeue(); // after work(), which may enqueue something else
}
return true;
}
public:
static bool enqueue(work_t work, unsigned long interval = 1000, bool repeat = true);
static bool doNext();
} workQueue[wqSize]; // ring buffer
void WorkItem::requeue() {
size_t tail = (wqHead + wqLen++) % wqSize;
WorkItem *item = workQueue + tail;
item->previousMillis = previousMillis;
item->interval = interval;
item->repeat = repeat;
item->work = work;
}
bool WorkItem::enqueue(work_t work, unsigned long interval, bool repeat) {
if (wqLen == wqSize) {
return false; // queue is full!
}
size_t tail = (wqHead + wqLen++) % wqSize;
WorkItem *item = workQueue + tail;
item->previousMillis = millis();
item->interval = interval;
item->repeat = repeat;
item->work = work;
return true;
}
bool WorkItem::doNext() {
const size_t tail = wqHead + wqLen;
size_t head = wqHead; // index may go past wqSize
while (head < tail) {
WorkItem *item = workQueue + wqHead;
wqHead = ++head % wqSize; // always [0, wqSize)
--wqLen;
if (item->doWork()) {
return true;
}
}
return false;
}
The rest of the sketch has two functions that represent work-to-do. One of them uses a static variable, which is one way of having per-function persistent data; but it could easily reference a global variable.
void blink() {
static int ledState = LOW;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(LED_BUILTIN, ledState);
}
void tick() {
Serial.println(millis());
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
WorkItem::enqueue(tick);
WorkItem::enqueue(blink, 750);
}
void loop() {
if (!WorkItem::doNext()) {
// If there was "nothing to do", waiting a little bit until checking again
// is "nicer" and may allow other tasks or an idle task, or use less power
delay(1);
}
}
setup enqueues two repeating items. loop then scans the queue. The work functions can enqueue other items. Each repeating item adds itself back; so the queue has more "traffic" than a "pure" queue of "stuff to do". Another simplification: the queue size is fixed, more than big enough.