According to asap documentation it uses a variety of techniques depending on which browser is it run or if run on node:
The following browsers allow the use of DOM mutation observers to access the HTML microtask queue, and thus begin flushing ASAP's task queue immediately at the end of the current event loop turn, before any rendering or IO:
Internet Explorer 11
iPad Safari 6–7.1
iPhone Safari 7–7.1
In the absense of mutation observers, there are a few browsers, and situations like web workers in some of the above browsers, where message channels would be a useful way to avoid falling back to timers. Message channels give direct access to the HTML task queue, so the ASAP task queue would flush after any already queued rendering and IO tasks, but without having the minimum delay imposed by timers. However, among these browsers, Internet Explorer 10 and Safari do not reliably dispatch messages, so they are not worth the trouble to implement.
Internet Explorer 10
In the absense of mutation observers, these browsers and the following browsers all fall back to using setTimeout and setInterval to ensure that a flush occurs. The implementation uses both and cancels whatever handler loses the race, since setTimeout tends to occasionally skip tasks in unisolated circumstances. Timers generally delay the flushing of ASAP's task queue for four milliseconds.
Internet Explorer 6–10
iPad Safari 4.3
Also, the setInmediate shim does somehting similar:
The setImmediate API, as specified, gives you access to the environment's task queue, sometimes known as its "macrotask" queue. This is crucially different from the microtask queue used by web features such as MutationObserver, language features such as promises and Object.observe, and Node.js features such as process.nextTick. Each go-around of the macrotask queue yields back to the event loop once all queued tasks have been processed, even if the macrotask itself queued more macrotasks. Whereas, the microtask queue will continue executing any queued microtasks until it is exhausted.
In practice, what this means is that if you call setImmediate inside of another task queued with setImmediate, you will yield back to the event loop and any I/O or rendering tasks that need to take place between those calls, instead of executing the queued task as soon as possible.