可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
we are using require.js in our project and we need to override the setTimeout in line 705 , this is the code which we need to ignore/omit somehow this setTimeout at all(I mean run over it) ,the problem that if I change it in the open source code explicit when I change version the code will be lost,How should I override this setTimout from outside only for the require.js file and keep it as long as I use this lib, is it possible to do it in elegant way in JS globally ?
https://github.com/jrburke/requirejs/blob/master/require.js
This is line 705
//If still waiting on loads, and the waiting load is something
//other than a plugin resource, or there are still outstanding
//scripts, then just try back later.
if ((!expired || usingPathFallback) && stillLoading) {
//Something is still waiting to load. Wait for it, but only
//if a timeout is not already in effect.
if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
checkLoadedTimeoutId = setTimeout(function () {
checkLoadedTimeoutId = 0;
checkLoaded();
}, 50);
}
}
FYI ,The reason that we do it is
Chrome: timeouts/interval suspended in background tabs?
回答1:
You've stated your goal is to work around the throttling that Chrome performs on setTimeout
for tabs that are in the background. I do not think it is a good idea to do so but if you must, then you should definitely patch RequireJS instead of messing with setTimeout
globally. You said:
if I change it in the open source code explicit when I change version the code will be lost
This is true only if you do not use a sensible method to perform the change. It is possible to do it sensibly. For instance, you can use Gulp to take the require.js
file installed in node_modules
(after you install RequireJS with npm
) and produce a patched file in build
. Then you use this patched file in your application. Here is the gulpfile.js
:
var gulp = require("gulp");
// Bluebird is a good implementation of promises.
var Promise = require("bluebird");
// fs-extra produces a `fs` module with additional functions like
// `ensureDirAsync` which is used below.
var fs = require("fs-extra");
// Make it so that for each the function in fs that is asynchronous
// and takes a callback (e.g. `fs.readFile`), a new function that
// returns promise is created (e.g. `fs.readFileAsync`).
Promise.promisifyAll(fs);
var to_replace =
"if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {\n\
checkLoadedTimeoutId = setTimeout(function () {\n\
checkLoadedTimeoutId = 0;\n\
checkLoaded();\n\
}, 50);";
var replace_with =
"if (isBrowser || isWebWorker) {\n\
checkLoaded();";
gulp.task("default", function () {
// Use `fs.ensureDirAsync` to make sure the build directory
// exists.
return fs.ensureDirAsync("build").then(function () {
return fs.readFileAsync("node_modules/requirejs/require.js")
.then(function (data) {
data = data.toString();
// We use the split/join idiom to a) check that we get
// the string to be replaced exactly once and b)
// replace it. First split...
var chunks = data.split(to_replace);
// Here we check that the results of splitting the
// chunk is what we expect.
if (chunks.length < 2) {
throw new Error("did not find the pattern");
}
else if (chunks.length > 2) {
throw new Error("found the pattern more than once");
}
// We found exactly one instance of the text to
// replace, go ahead. So join...
return fs.writeFileAsync("build/require.js",
chunks.join(replace_with));
});
});
});
You need to have run npm install gulp fs-extra bluebird requirejs
before running it. At any rate, you can use Gulp, you can use Grunt, or you can use any other system you want to perform a build. The points are:
You have a reproducible and automated method to patch RequireJS. If you install a new version of RequireJS with npm
, when you rebuild your software the patch is applied automatically, so long as the code of RequireJS does not change in a way that prevents applying the patch. See the next point for what happens if a change prevents applying the patch.
This method is more robust than overriding setTimeout
at runtime. Suppose James Burke decides in a newer version of RequireJS to rename checkLoaded
to checkDone
and renames the associated variables (so that checkLoadedTimeoutId
becomes checkDoneTimeoutId
). The gulpfile above will raise an exception when you run it again because it won't find the text to be replaced. You'll have to update the text to be replaced and the replacement so that the patch works with the new version of RequireJS. The benefit here is that you get an early warning that things have changed and that you need to review the patch. You won't have a surprise late in the game, perhaps after you've already delivered a new version of your software to clients.
The methods that override setTimeout
at run time will just silently fail to do their job. They'll be looking for a function that contains checkLoadedTimeoutId
, which won't exist anymore in the new version. So they will just let RequireJS behave the way it does by default. The failure will be a subtle one. (I've run RequireJS with the proposed custom versions of setTimeout
with a project that loads upwards of 50 modules when not optimized. I saw no discernible difference between RequireJS using the stock setTimeout
and RequireJS using a custom setTimeout
.)
This method does not slow down every use of setTimeout
. setTimeout
is used by other code than RequireJS. No matter how you cut it, adding code in a custom replacement to setTimeout
that starts looking for strings in each function passed to it will make all uses of setTimeout
slower.
回答2:
You can override setTimeout
and check if the function passed as callback contains a variable used in that function from require.js (checkLoadedTimeoutId
). If yes, call function immediately, otherwise call original setTimeout
function.
(function(setTimeoutCopy) {
setTimeout = function(fn, timeout) {
if (fn.toString().indexOf("checkLoadedTimeoutId") >= 0) {
return fn();
} else {
return setTimeoutCopy.apply(null, arguments);
}
};
}(setTimeout));
Note that there are multiple issues with this code. If you pass a function to setTimeout
which contains checkLoadedTimeoutId
, it will be executed immediately too. Also, if require.js code is minified and variables are renamed, it won't work.
To sum up, there is no good way do this. Maybe try to find a different way to achieve what you want. Also be aware that, as Madara Uchiha said:
Changing global functions and objects is almost never a good idea.
回答3:
If you really need this... Try add before loading requirejs:
function isTimeoutIgnoredFunction(fn, time) {
// you should configure this condition for safety, to exclude ignoring different timeOut calls!
return time == 50 && fn.toString().indexOf('checkLoadedTimeoutId') > -1;
}
window.setTimeoutOriginal = window.setTimeout;
window.setTimeout = function(fn, time) {
if (isTimeoutIgnoredFunction(fn, time)) {
return fn(); // or return true if you don't need to call this
} else {
return window.setTimeoutOriginal.apply(this, arguments);
}
};
Should work in last Chrome, Firefox, IE if no requirejs minify provided... You need to rewrite function "isTimeoutIgnoredFunction" for browsers you support and for minified require.js file.
To see what string you can use:
console.log((function () {
checkLoadedTimeoutId = 0;
checkLoaded();
}).toString());
But in some browsers it can just something like "Function".
If you have not many setTimeout's it can be suitable solution...
回答4:
Other way to achieve it is making a ajax request of the library and patch it before load the library, but you will need to have loaded a way to make the request (vanilla js or jquery...)
The next code is an example of loading requirejs and patch it with a regexp before load it in the DOM.
$(function(){
// here you can put the url of your own hosted requirejs or a url from a CDN
var requirejsUrl = 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.22/require.js';
function patch(lib){
var toReplace = /if\s*\(\(isBrowser\s*\|\|\s*isWebWorker\)\s*\&\&\s*!checkLoadedTimeoutId\)\s*\{([^{}]|\{[^{}]*\})*\}/;
var by = 'if (isBrowser || isWebWorker) { checkLoaded();}';
return lib.replace(toReplace, by);
}
$.get(requirejsUrl, function(lib){
var libpatched = patch(lib);
var script=document.createElement('script');
script.innerText=libpatched;
$('body').append(script);
console.log(window.require); // requirejs patched is loaded
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>