Last active
March 14, 2021 15:54
-
-
Save renanlecaro/2ab7fbcd045b0592613fbd6103429191 to your computer and use it in GitHub Desktop.
Rate limiting for Meteor in 2021
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Ensures that a function "fn" is only run every "delay" milliseconds. | |
Let's say you have two meteor users, and they want to call a function repeatedly to send 300 emails each. | |
Your mailing API blocks you if you go over 2 req per second. | |
Your SERVER code would look like this | |
// the expensive function | |
function rawSendMail(a,b,c){ | |
// (this==null here) | |
return callSomeExpensiveFunctionThatReturnsResult() | |
} | |
// the rate limited version, will not run more than twice a second | |
const sendMail = rateLimit(rawSendMail, 550) ; | |
// just call it synchronously in meteor | |
Meteor.methods({ | |
mailBatch(listOfEmails){ | |
return listOfEmails.map(sendMail) | |
} | |
}) | |
*/ | |
export function rateLimit(fn, delay) { | |
var queue = [], | |
lastRun = 0, | |
running = false; | |
function delayTillNext() { | |
return Math.max(0, delay - (Date.now() - lastRun)); | |
} | |
function processQueue() { | |
lastRun = Date.now(); | |
var item = queue.shift(); | |
if (item) { | |
try { | |
item.resolve(fn.apply(null, item.arguments)); | |
} catch (e) { | |
item.reject(e); | |
} | |
} | |
if (queue.length === 0) { | |
running = false; | |
} else { | |
running = true; | |
Meteor.setTimeout(processQueue, delayTillNext()); | |
} | |
} | |
return function limited() { | |
const item = { | |
arguments: [].slice.call(arguments) | |
}; | |
const promise = new Promise((resolve, reject) => { | |
item.resolve = resolve; | |
item.reject = reject; | |
}); | |
queue.push(item); | |
if (!running) { | |
running = true; | |
Meteor.setTimeout(processQueue, delayTillNext()); | |
} | |
return promiseToSync(promise); | |
}; | |
} | |
// Little helper to make server code wait for a Promise, there's surely a better way | |
function promiseToSync(promise) { | |
return Meteor.wrapAsync(cb => | |
promise.then( | |
res => cb(null, res), | |
err => cb(err) | |
) | |
)(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment