在数组中使用 setInterval() 和 .map 方法并作为承诺返回

Wel*_*lls 2 javascript arrays api timer promise

我有一个函数,它获取 ID 列表,将它们转换为数组 URL,然后使用 map 函数来触发获取请求。它工作得很好,但启动速度太快,并且提供者会抛出错误,因为我们太频繁地访问 API。我需要为请求设置一个时间间隔,但每次这样做都不起作用。有想法吗?

async function getReports(reportIDs) {
    const urls = reportIDs.map(id => `https://api.data.com/api/v1/report/${id}/?include_datasets=true`);
    const requests = urls.map(url => fetch(url, {
        method: 'GET',
        headers: { 'api-key': key }
    }).then(res => res.json()));
    
    const responses = await Promise.all(requests).catch(err => console.error(err));
    return responses;
}
Run Code Online (Sandbox Code Playgroud)

我用一个承诺,这样我就可以await在另一个函数中使用该函数的结果来转换数据集。

有想法吗?

Tha*_*you 6

\xe2\x80\x9c简单是一种伟大的美德,但它需要努力工作才能实现,并需要接受教育才能欣赏它。更糟糕的是:复杂性卖得更好。\xe2\x80\x9d \xe2\x80\x94 Edsger W. Dijkstra

\n

公认的“轻量级”解决方案近2万行代码,并且依赖于 CoffeeScript 和 Lua。如果您可以用所有这些来换取 50 行 JavaScript 代码会怎么样?

\n

假设我们有一些job需要一些时间来计算一些结果 -

\n
async function job(x) {\n  // job consumes some time\n  await sleep(rand(5000))\n  // job computes a result\n  return x * 10\n}\n\nPromise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(job))\n  .then(console.log, console.error)\n
Run Code Online (Sandbox Code Playgroud)\n
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]\n
Run Code Online (Sandbox Code Playgroud)\n

这会同时运行所有十二 (12) 个作业。如果这些是对远程的请求,则某些连接可能会被拒绝,因为过多的并发流量淹没了服务器。通过对Pool线程进行建模,我们控制并行作业的流程 -

\n
// my pool with four threads\nconst pool = new Pool(4)\n\nasync function jobQueued(x) {\n  // wait for pool thread\n  const close = await pool.open()\n  // run the job and close the thread upon completion\n  return job(x).then(close)\n}\n\nPromise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(jobQueued))\n  .then(console.log, console.error)\n
Run Code Online (Sandbox Code Playgroud)\n
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]\n
Run Code Online (Sandbox Code Playgroud)\n

函数应该很小并且只做一件事。这使得编写单独的功能变得更加容易,并提高了可重用性,允许您将几个简单的功能组合成更复杂的功能。上面你已经看到randsleep-

\n
const rand = x =>\n  Math.random() * x\n\nconst sleep = ms =>\n  new Promise(r => setTimeout(r, ms))\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们想要throttle每项工作,我们可以专门sleep确保最短的运行时间 -

\n
const throttle = (p, ms) =>\n  Promise.all([ p, sleep(ms) ]).then(([ value, _ ]) => value)\n\nasync function jobQueued(x) {\n  const close = await pool.open()\n  // ensure job takes at least 3 seconds before freeing thread\n  return throttle(job(x), 3000).then(close)\n}\n\nPromise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(jobQueued))\n  .then(console.log, console.error)\n
Run Code Online (Sandbox Code Playgroud)\n
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]\n
Run Code Online (Sandbox Code Playgroud)\n

我们可以添加一些console.log消息以确保一切正常运行。我们将sleep在作业的开头添加一个随机数,以表明任务可以按任意顺序排队,而不会影响结果的顺序 -

\n
async function jobQueued(x) {\n  await sleep(rand(5000))\n  console.log("queueing", x)\n  const close = await pool.open()\n  console.log("  sending", x)\n  const result = await throttle(job(x), 3000).then(close)\n  console.log("    received", result)\n  return result\n}\n\nPromise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(jobQueued))\n  .then(console.log, console.error)\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
控制台日志线程1线程2线程3线程4
排队12\xe2\x8c\x9b\xe2\x8c\x9b\xe2\x8c\x9b\xe2\x8c\x9b
\xc2\xa0\xc2\xa0\xc2\xa0发送 12打开\xe2\x8c\x9b\xe2\x8c\x9b\xe2\x8c\x9b
排队9\xe2\x86\x93\xe2\x8c\x9b\xe2\x8c\x9b\xe2\x8c\x9b
\xc2\xa0\xc2\xa0\xc2\xa0发送 9\xe2\x86\x93打开\xe2\x8c\x9b\xe2\x8c\x9b
排队8\xe2\x86\x93\xe2\x86\x93\xe2\x8c\x9b\xe2\x8c\x9b
\xc2\xa0\xc2\xa0\xc2\xa0发送 8\xe2\x86\x93\xe2\x86\x93打开\xe2\x8c\x9b
排队4\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\xe2\x8c\x9b
\xc2\xa0\xc2\xa0\xc2\xa0发送 4\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93打开
排队 10\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
排队6\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
排队7\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
排队2\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
排队11\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 120关闭\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0发送 11打开\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
排队3\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
排队5\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
排队1\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 80\xe2\x86\x93\xe2\x86\x93关闭\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0发送 1\xe2\x86\x93\xe2\x86\x93打开\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 90\xe2\x86\x93关闭\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0发送 5\xe2\x86\x93打开\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 110关闭\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0发送 3打开\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 40\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93关闭
\xc2\xa0\xc2\xa0\xc2\xa0发送 2\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93打开
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 10\xe2\x86\x93\xe2\x86\x93关闭\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0发送 7\xe2\x86\x93\xe2\x86\x93打开\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 50\xe2\x86\x93关闭\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0发送 6\xe2\x86\x93打开\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 20\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93关闭
\xc2\xa0\xc2\xa0\xc2\xa0发送 10\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93打开
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 30关闭\xe2\x86\x93\xe2\x86\x93\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 70\xe2\x8c\x9b\xe2\x86\x93关闭\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 60\xe2\x8c\x9b关闭\xe2\x8c\x9b\xe2\x86\x93
\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0收到 100\xe2\x8c\x9b\xe2\x8c\x9b\xe2\x8c\x9b关闭
\n
\n
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]\n
Run Code Online (Sandbox Code Playgroud)\n

上面,我们pool进行了初始化,size=4因此最多可以同时运行四个作业。在我们看sending四次之后,一项工作必须完成,并且我们received在下一项工作开始之前看到。queueing随时可能发生。您可能还注意到Pool使用高效的后进先出 (LIFO) 顺序处理排队作业,但结果的顺序保持不变。

\n

继续我们的实现,就像我们的其他函数一样,我们可以thread用简单的方式编写 -

\n

\r\n
\r\n
const effect = f => x =>\n  (f(x), x)\n\nconst thread = close =>\n  [new Promise(r => { close = effect(r) }), close]\n\nfunction main () {\n  const [t, close] = thread()\n  console.log("please wait...")\n  setTimeout(close, 3000)\n  return t.then(_ => "some result")\n}\n\nmain().then(console.log, console.error)
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

please wait...\n(3 seconds later)\nsome result\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们可以用来thread编写更复杂的功能,例如Pool-

\n
class Pool {\n  constructor (size = 4) {\n    Object.assign(this, { pool: new Set, stack: [], size })\n  }\n  open () {\n    return this.pool.size < this.size\n      ? this.deferNow()\n      : this.deferStacked()\n  }\n  deferNow () {\n    const [t, close] = thread()\n    const p = t\n      .then(_ => this.pool.delete(p))\n      .then(_ => this.stack.length && this.stack.pop().close())\n    this.pool.add(p)\n    return close\n  }\n  deferStacked () {\n    const [t, close] = thread()\n    this.stack.push({ close })\n    return t.then(_ => this.deferNow())\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

就这样你的程序就完成了。在下面的功能演示中,我浓缩了定义,以便我们可以立即看到它们。运行程序以在您自己的浏览器中验证结果 -

\n

\r\n
\r\n
class Pool {\n  constructor (size = 4) { Object.assign(this, { pool: new Set, stack: [], size }) }\n  open () { return this.pool.size < this.size ? this.deferNow() : this.deferStacked() }\n  deferNow () { const [t, close] = thread(); const p = t.then(_ => this.pool.delete(p)).then(_ => this.stack.length && this.stack.pop().close()); this.pool.add(p); return close }\n  deferStacked () { const [t, close] = thread(); this.stack.push({ close }); return t.then(_ => this.deferNow()) }\n}\nconst rand = x => Math.random() * x\nconst effect = f => x => (f(x), x)\nconst thread = close => [new Promise(r => { close = effect(r) }), close]\nconst sleep = ms => new Promise(r => setTimeout(r, ms))\nconst throttle = (p, ms) => Promise.all([ p, sleep(ms) ]).then(([ value, _ ]) => value)\n\nconst myJob = x => sleep(rand(5000)).then(_ => x * 10)\nconst pool = new Pool(4)\n\nasync function jobQueued(x) {\n  await sleep(rand(5000))\n  console.log("queueing", x)\n  const close = await pool.open()\n  console.log("  sending", x)\n  const result = await throttle(myJob(x), 3000).then(close)\n  console.log("    received", result)\n  return result\n}\n\nPromise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(jobQueued))\n  .then(JSON.stringify)\n  .then(console.log, console.error)
Run Code Online (Sandbox Code Playgroud)\r\n
.as-console-wrapper { min-height: 100%; }
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

希望您学到了一些关于 JavaScript 的有趣知识!如果您喜欢这个,请尝试扩展Pool功能。也许添加一个简单的timeout函数来确保作业在一定的时间内完成。或者,也许添加一个retry函数,在作业产生错误或超时时重新运行该作业。要查看Pool是否适用于其他问题,请参阅此问答。如果您有任何疑问,我很乐意为您提供帮助:D

\n