My guess is that this was a problem they were trying to solve but had not, so they were asking candidates for a solution, and they would be impressed with anyone who had one. My hope would be that it was a trick question and they knew it and wanted to see if you did.
Given their definitions, the task is impossible using what was widely supported in 2017. It is sort of possible in 2019 using ServiceWorkers.
As you know, in the browser, the window runs in a single thread that runs an event loop. Everything that is asynchronous is some kind of deferred task object that is put on a queue for later execution1. Communications between the window's threads and workers' threads are asynchronous, Promises are asynchronous, and generally XHR's are done asynchronously.
If you want to call a synchronous script, you need to make a call that blocks the event loop. However, JavaScript does not have interrupts or a pre-emptive scheduler, so while the event loop is blocked, there is nothing else that can run to cause it to abort. (That is to say, even if you could spin up a worker thread that the OS runs in parallel with the main thread, there is nothing the worker thread can do to cause the main thread to abort the read of the script.) The only hope you could have of having a timeout on the fetch of the script is if there were a way to set an operating-system-level timeout on the TCP request, and there is not. The only way to get a script other than via the HTML script
tag, which has no way to specify a timeout, is XHR
(short for XMLHttpRequest
) or fetch
where it is supported. Fetch
only has an asynchronous interface, so it is no help. While it is possible to make a synchronous XHR
request, according to MDN (the Mozilla Developer's Network), many browsers have deprecated synchronous XHR support on the main thread entirely. Even worse, XHR.timeout()
"shouldn't be used for synchronous XMLHttpRequests requests used in a document environment or it will throw an InvalidAccessError exception."
So if you block the main thread to wait for the synchronous loading of the JavaScript, you have no way to abort the loading when you have decided it is taking too long. If you do not block the main thread, then the script will not execute until after the page has loaded.
Q.E.D
Partial Solution with Service Workers
@Kaiido argues this can be solved with ServiceWorkers. While I agree that ServiceWorkers were designed to solve problems like this, I disagree that they answer this question for a few reasons. Before I get into them, let me say that I think Kaiido's solution is fine in the much more general case of having a Single Page App that is completely hosted on HTTPS implement some kind of timeout for resources to prevent the whole app from locking up, and my criticisms of that solution are more about it being a reasonable answer to the interview question than any failing of the solution overall.
- OP said the question came from "a long time ago" and service worker support in production releases of Edge and Safari is less than a year old. ServiceWorkers are still not considered "standard" and
AbortController
is still today not fully supported.
- In order for this to work, Service Workers have to be installed and configured with a timeout prior to the page in question being loaded. Kaiido's solution loads a page to load the service workers and then redirects to the page with the slow JavaScript source. Although you can use
clients.claim()
to start the service workers on the page where they were loaded, they still will not start until after the page is loaded. (On the other hand, they only have to be loaded once and they can persist across browser shutdowns, so in practice it is not entirely unreasonable to assume that the service workers have been installed.)
- Kaiido's implementation imposes the same timeout for all resources fetched. In order to apply only to the script URL in question, the service worker would need to be preloaded with the script URL before the page itself was fetched. Without some kind of whitelist or blacklist of URLs, the timeout would apply to loading the target page itself as well as loading the script source and every other asset on the page, synchronous or not. While of course it is reasonable in this Q&A environment to submit an example that is not production ready, the fact that to limit this to the one URL in question means that the URL needs to be separately hard coded into the service worker makes me uncomfortable with this as a solution.
ServiceWorkers only work on HTTPS content. I was wrong. While ServiceWorkers themselves have to be loaded via HTTPS, they are not limited to proxying HTTPS content.
That said, my thanks to Kaiido for providing a nice example of what a ServiceWorker can do.
1There is an excellent article from Jake Archibald that explains how asynchronous tasks are queued and executed by the window's event loop.