```javascript
const timer = new Timer()
timer
.call(() => console.log("begin"))
.call(() => console.log("should wait 2"))
.waitFor(2)
.call(() => console.log("should wait 3"))
.waitFor(1)
.waitFor(1)
.waitFor(1)
.call(() => console.log("done"))
timer
.waitFor(1)
.call(() => console.log("kek"))
function Timer() {
const Kind = {
CB: 0,
DELAY: 1,
}
const queue = []
let first = true
// detached from sync loop
function initDetachedProcessing() {
// // len === 1, because will only happen once -
// // when the queue is pushed into initially.
// // this will create a micro task, that will only happen
// // after more items have been added to the queue
// // see:
// // - https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide#batching_operations
//
// instead of len === 1, just use `first` one-time toggle -
// intentions clearer & simpler to understand
if (first) {
first = false
// queue microtask, in order to collect all items into the queue
// (sync Task processing), and only then to perform the processing (microtask)
//
// see:
// - https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide#tasks_vs_microtasks
// > The event loop driving your code handles these tasks one after
// > another, in the order in which they were enqueued. The oldest
// > runnable task in the task queue will be executed during a single
// > iteration of the event loop. After that, microtasks will be
// > executed until the microtask queue is empty, and then the
// > browser may choose to update rendering. Then the browser moves
// > on to the next iteration of event loop.
queueMicrotask(() => {
processNextQueueItem()
})
}
}
function processNextQueueItem() {
if (!queue.length) return
const [kind, item] = queue.shift()
if (kind === Kind.CB) {
item() // cb
processNextQueueItem()
} else if (kind === Kind.DELAY) {
const ms = item * 1000 // s
setTimeout(() => {
processNextQueueItem()
}, ms)
} else {
throw new Error("never")
}
}
return {
call(cb) {
queue.push([Kind.CB, cb])
initDetachedProcessing()
return this
},
waitFor(s) {
queue.push([Kind.DELAY, s])
initDetachedProcessing()
return this
},
}
}
```