JS 异步/等待任务队列

2024-01-04

在我的 JS 应用程序中,我使用 async/await 功能。我想执行多个 API 调用,并希望它们被一个接一个地调用。换句话说,我想替换这个简单的方法:

const addTask = async (url, options) => {
    return await fetch(url, options)
}

更复杂的东西..比如:

let tasksQueue = []
const addTask = async (url, options) => {
    tasksQueue.push({url, options})
    ...// perform fetch in queue
    return await ...
}

处理异步返回的最佳方法是什么?


你可以使用Queue https://en.wikipedia.org/wiki/Queue_(abstract_data_type)#Examples数据结构作为基础并在子类中添加特殊行为。 AQueue有两个方法的众所周知的接口enqueue() (add新项目结束)和dequeue() (remove第一项)。在你的情况下dequeue()等待异步任务。

特殊行为:

  1. 每次新任务(例如fetch('url')) gets enqueued, this.dequeue()被调用。
  2. What dequeue() does:
    1. 如果队列为空 ➜return false(跳出递归)
    2. 如果队列繁忙 ➜return false(上一个任务未完成)
    3. 否则 ➜ 删除first从队列中取出任务并运行它
    4. 任务“完成”(成功或有错误)➜ 递归调用dequeue()(2.),直到队列为空..
class Queue {
  constructor() { this._items = []; }
  enqueue(item) { this._items.push(item); }
  dequeue()     { return this._items.shift(); }
  get size()    { return this._items.length; }
}

class AutoQueue extends Queue {
  constructor() {
    super();
    this._pendingPromise = false;
  }

  enqueue(action) {
    return new Promise((resolve, reject) => {
      super.enqueue({ action, resolve, reject });
      this.dequeue();
    });
  }

  async dequeue() {
    if (this._pendingPromise) return false;

    let item = super.dequeue();

    if (!item) return false;

    try {
      this._pendingPromise = true;

      let payload = await item.action(this);

      this._pendingPromise = false;
      item.resolve(payload);
    } catch (e) {
      this._pendingPromise = false;
      item.reject(e);
    } finally {
      this.dequeue();
    }

    return true;
  }
}

// Helper function for 'fake' tasks
// Returned Promise is wrapped! (tasks should not run right after initialization)
let _ = ({ ms, ...foo } = {}) => () => new Promise(resolve => setTimeout(resolve, ms, foo));
// ... create some fake tasks
let p1 = _({ ms: 50, url: '❪????❫', data: { w: 1 } });
let p2 = _({ ms: 20, url: '❪????❫', data: { x: 2 } });
let p3 = _({ ms: 70, url: '❪????❫', data: { y: 3 } });
let p4 = _({ ms: 30, url: '❪????❫', data: { z: 4 } });

const aQueue = new AutoQueue();
const start = performance.now();

aQueue.enqueue(p1).then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); //          = 50
aQueue.enqueue(p2).then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // 50 + 20  = 70
aQueue.enqueue(p3).then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // 70 + 70  = 140
aQueue.enqueue(p4).then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // 140 + 30 = 170

互动演示:

完整代码演示:https://codesandbox.io/s/async-queue-ghpqm?file=/src/index.js https://codesandbox.io/s/async-queue-ghpqm?file=/src/index.js您可以在控制台和/或开发工具的“性能”选项卡中尝试并观察结果。这个答案的其余部分是基于它的。

Explain:

enqueue()返回一个new Promise,这将是resolved(或拒绝)稍后的某个时刻. This Promise可用于处理您的响应async任务 Fn.

enqueue()实际上push() an Object进入队列,即holds任务 Fn 和控制方式对于返回的 Promise。

自从展开的Promise瞬间。开始运行,this.dequeue()每次我们将新任务加入队列时都会被调用。

和一些性能指标() https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure添加到我们的task,我们得到了队列的良好可视化:

enter image description here (*.gif animation)

  • 1st row是我们的队列实例
  • 新入队tasks有一个“❚❚等待..”时期(3nd row) (可能< 1ms如果队列为空`)
  • 在某些时候,它会出列并“▶运行..”一段时间(2nd row)

日志输出(控制台.表() http://console.table):

解释: 第一名task is enqueue()d at 2.58ms就在队列初始化之后。 由于我们的队列是空的,所以就像没有❚❚ waiting (0.04ms➜ ~40μm)。 任务运行时间13.88ms➜ 出队


Class Queue is 只是一个包装纸对于本地人Array Fn´s!

您当然可以在一堂课中实现这一点。我只是想表明,您可以从已知的数据结构中构建您想要的内容。有一些充分的理由不使用Array:

  • A Queue数据结构由一个定义界面两个公共方法。使用Array可能会诱使其他人使用本机Array其上的方法就像.reverse(),...这会打破定义 https://en.wikipedia.org/wiki/Queue_(abstract_data_type)#Examples.
  • enqueue() and dequeue()push() and shift()
  • 如果您已经有一个已实施的Queue类,您可以从中扩展(可重用代码)
  • 您可以更换该物品Array in class Queue通过另一个数据结构:A“双向链表 https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/#Queue-implemented-with-a-Doubly-Linked-List“这会减少代码复杂度 for Array.shift()从 O(n) [线性] 到 O(1) [常数]。 (➜ 比原生数组 Fn 更好的时间复杂度!)(➜ 最终演示)

代码限制

This AutoQueue类不限于async功能。它处理anything,可以这样称呼await item[MyTask](this):

  1. let task = queue => {..}sync功能
  2. let task = async queue => {..}async功能
  3. let task = queue => new Promise(resolve => setTimeout(resolve, 100)new Promise()

注意:我们已经用以下方式调用我们的任务await, where await wraps将任务的响应转化为Promise。 编号 2。(async函数),总是返回一个Promise就其本身而言,并且await调用只是包装一个Promise进入另一个Promise,效率稍低。 Nr 3. 没问题。返回的承诺将not被包裹await

这是异步函数的执行方式:(source https://exploringjs.com/es2016-es2017/ch_async-functions.html#_async-functions-are-started-synchronously-settled-asynchronously)

  1. 异步函数的结果始终是 Promisep。该 Promise 是在开始执行异步函数时创建的。
  2. 尸体被处决。执行可以通过返回或抛出永久完成。或者可以通过await暂时结束;在这种情况下,执行通常会稍后继续。
  3. 承诺p被返回。

以下代码演示了其工作原理:

async function asyncFunc() {
    console.log('asyncFunc()'); // (A)
    return 'abc';
}
asyncFunc().
    then(x => console.log(`Resolved: ${x}`)); // (B)
console.log('main'); // (C)

// Output:
// asyncFunc()
// main
// Resolved: abc

您可以依赖以下顺序:

  1. (A) 行:异步函数同步启动。异步函数的 Promise 通过 return 来解析。
  2. (C) 行:继续执行。
  3. 第 (B) 行:Promise 解析通知异步发生。

阅读更多: ”可调用值 https://exploringjs.com/impatient-js/ch_callables.html” 阅读更多: ”异步函数 https://exploringjs.com/es2016-es2017/ch_async-functions.html"


性能限制

Since AutoQueue仅限于处理one task 在另一个之后,它可能会成为我们应用程序的瓶颈。限制因素有:

  1. 每次任务:➜ 新产品的频率enqueue()d tasks.
  2. 每个任务的运行时间➜ 阻塞时间dequeue()直到任务完成

1.每次任务

这是我们的责任!我们可以得到当前的大小queue随时:size = queue.size. Your outer脚本需要一个“故障转移”案例来稳定增长的队列(检查“Stackedwait次”部分)。

您想避免像这样的“队列溢出”,其中平均值/平均值waitTime随着时间的推移而增加。

+-------+----------------+----------------+----------------+----------------+
| tasks | enqueueMin(ms) | enqueueMax(ms) | runtimeMin(ms) | runtimeMax(ms) |
| 20    |              0 |            200 |             10 |             30 |
+-------+----------------+----------------+----------------+----------------+
  • ➜ Task 20/20等待195ms直到执行开始
  • ➜ 从我们上一个任务随机入队开始,又需要另一个任务+ ~232ms,直到所有任务都解决了。

2. 每个任务的运行时间

这个就比较难对付了。 (等待一个fetch()无法改进,需要等待 HTTP 请求完成)。
也许你的fetch()任务依赖于彼此的响应,较长的运行时间会阻塞其他任务。

但我们可以做一些事情:

  • 也许我们可以缓存响应➜ 减少下一次排队的运行时间。

  • 也许我们fetch()来自 CDN 并有一个我们可以使用的替代 URI。在这种情况下我们可以返回一个new Promise从我们的task将在下一个之前运行task is enqueue()d. (参见“错误处理”):

    queue.enqueue(queue => Promise.race(fetch('url1'), fetch('url2')));
    
  • 也许你有某种“长轮询 https://javascript.info/long-polling" 或周期性 ajaxtask每 x 秒运行一次,无法缓存。即使您无法减少运行时间本身,您也可以记录运行时间,这将为您提供一个近似值。下一次运行的估计。也许可以将长时间运行的任务交换到其他队列实例。


均衡AutoQueue

什么是“高效”Queue? - 你的第一个想法可能是这样的:

最有效率的Queue处理大多数tasks在最短的时间内?

既然我们无法改善我们的task运行时,我们可以降低等待时间时间?该示例是一个queue with zero (~0ms) 任务之间的等待时间。

提示:为了比较我们的下一个示例,我们需要一些base不会改变的统计数据:

+-------+----------------+----------------+------------------+------------------+
| count |  random fake runtime for tasks  |  random enqueue() offset for tasks  |
+-------+----------------+----------------+------------------+------------------+
| tasks | runtimeMin(ms) | runtimeMax(ms) | msEnqueueMin(ms) | msEnqueueMax(ms) |
| 200   |             10 |             30 |                0 |             4000 |
+-------+----------------+----------------+------------------+------------------+
     Avg. task runtime: ⇒ (10ms + 30ms) / 2   = 20ms
     Total time:        ⇒  20ms * 200         = 4000ms ≙ 4s
  ➜ We expect our queue to be resolved after ~4s
  ➜ For consistent enqueue() frequency we set msEnqueueMax to 4000
  • AutoQueue最后完成dequeue() after ~4.12s(^^ 请参阅工具提示)。
  • Which is ~120ms longer than our expected 4s:

    提示:每个任务后都有一个小的“日志”块~0.3ms,我在那里构建/推送一个Object带有日志标记到全局“数组”console.table()记录在最后。这解释了200 * 0.3ms = 60ms..失踪者60ms未跟踪(您会看到任务之间的小间隙)->0.3ms/task 用于我们的测试循环,并且可能会因打开开发工具而出现一些延迟,..

我们稍后再讨论这些时间安排。

我们的初始化代码queue:

const queue = new AutoQueue();
// .. get 200 random Int numbers for our task "fake" runtimes [10-30]ms
let runtimes = Array.from({ length: 200 }, () => rndInt(10, 30));
let i = 0;
let enqueue = queue => {
    if (i >= 200) {
        return queue; // break out condition
    }
    i++;
    queue
        .enqueue(
            newTask({ // generate a "fake" task with of a rand. runtime
                ms: runtimes[i - 1],
                url: _(i)
            })
        )
        .then(payload => {
            enqueue(queue);
        });
};
enqueue(queue); // start recurion

我们递归地enqueue()我们的下一个任务,就在上一个任务完成之后。您可能已经注意到analogy to a typical Promise.then()链子,对吧?

提示:我们不需要Queue如果我们已经know的顺序和总数tasks按顺序运行。我们可以使用一个Promise链并得到相同的结果。

有时我们在脚本开始时并不知道所有后续步骤。

..你可能需要更多灵活性,以及next我们要运行的任务取决于前一个任务的响应task。 - 也许您的应用程序依赖于 REST API(多个端点),并且您的并发 API 请求数被限制为最多 X 个。我们无法向 API 发送来自您应用程序各处的请求。你甚至不知道下一个请求何时收到enqueue()d(例如 API 请求由以下条件触发)click()事件?..

好的,对于下一个示例,我稍微更改了初始化代码:

我们现在入队 200 个任务randomly在[0-4000ms]期间内。 - 公平地说,我们将范围缩小了30ms(最大任务运行时间)至 [0-3970ms]。现在我们的随机填充队列有机会留在里面4000ms limit.

我们可以通过开发工具性能登录得到什么:

  • Random enqueue() leads to a big number of "waiting" tasks.

    这是有道理的,因为我们首先将所有任务排入队列~4000ms,它们必须以某种方式重叠。检查表输出我们可以验证:Maxqueue.size is 22当时的任务170/200已排队。

  • Waiting tasks are not evenly distributed. Right after start there are even some idle section.

    由于随机性enqueue()它不太可能得到0ms我们的第一个任务的偏移量。~20ms每个任务的运行时间导致stacking随着时间的推移效果。

  • We can sort tasks by "wait ms" (see screen): Longest waiting time was >400ms.

    之间可能存在某种关系queue.size(柱子:sizeOnAdd) and wait ms(参见下一节)。

  • Our AwaitQueue completed last dequeue() ~4.37s after its initialization (check tooltip in "performance" tab). An average runtime of 20,786ms / task (expected: 20ms) gives us a total runtime of 4157.13ms (expected: 4000ms4s).

    我们仍然有“Log”块和执行程序。我们的测试脚本本身的时间~120ms. Still ~37ms更长?从一开始就总结所有闲置的“间隙”,解释了缺失的情况~37ms

回到我们最初的“定义”

最有效率的Queue处理大多数tasks在最短的时间内?

假设:除了随机偏移之外,tasks get enqueue()d 在前面的示例中,both队列处理了相同的号码 of tasks (平均数相等运行)内同一时间段。既没有waiting排队时间task也不queue.size影响总运行时间。两者的效率相同吗?

Since a Queue就其本质而言,缩小了我们编码的可能性,最好不要使用Queue如果我们谈论高效的代码(每次任务)。

队列可以帮助我们拉直异步环境中的任务转换为同步模式。这正是我们想要的。 ➜“运行unknown中的任务顺序a row".

如果你发现自己问这样的问题:“如果一个新的task已排入队列filled排队,我们必须的时间wait对于我们的结果来说,是通过其他人的运行时间来增加的。这样效率就低了!” 那么你就做错了:

  • 您可以将彼此没有依赖性(以某种方式)的任务排入队列(逻辑或编程依赖性),或者存在不会增加脚本总运行时间的依赖性。 - 无论如何,我们必须等待其他人。

Stacked wait times

我们看到了一个高峰wait的时间461.05ms在任务运行之前。如果我们能够预测,那不是很好吗?wait在我们决定将任务排入队列之前,时间是多少?

首先我们分析我们的行为AutoQueue上课时间较长。 (重新发布屏幕)

我们可以根据什么构建图表console.table() output:

旁边wait的时间task,我们可以看到随机的[10-30ms]runtime和 3 条曲线,代表当前queue.size,记录在时间atask ..

  • .. is enqueued()
  • ..开始运行。 (dequeue())
  • ..任务完成(就在下一个任务之前)dequeue())

另外 2 次运行进行比较(相似趋势):

  • 图表运行 2:https://i.stack.imgur.com/NGB3K.png https://i.stack.imgur.com/NGB3K.png
  • 图表运行 3:https://i.stack.imgur.com/NaQ39.png https://i.stack.imgur.com/NaQ39.png

我们能找到彼此之间的依赖关系吗?

如果我们能够找到任何记录的图表线之间的关系,它可能会帮助我们理解queue随着时间的推移而表现(➜不断充满新任务)。

Exkurs:什么是关系? 我们正在寻找一个方程projects the wait ms curve onto3 之一queue.size记录。这将证明两者之间的直接依赖关系。

对于上次运行,我们更改了启动参数:

  • 任务数:2001000 (5x)

  • msEnqueueMax:4000ms20000ms (5x)

    +-------+----------------+----------------+------------------+------------------+
    | count |  random fake runtime for tasks  |  random enqueue() offset for tasks  |
    +-------+----------------+----------------+------------------+------------------+
    | tasks | runtimeMin(ms) | runtimeMax(ms) | msEnqueueMin(ms) | msEnqueueMax(ms) |
    | 1000  |             10 |             30 |                0 |            20000 |
    +-------+----------------+----------------+------------------+------------------+
         Avg. task runtime: ⇒ (10ms + 30ms) / 2   = 20ms (like before)
         Total time:        ⇒  20ms * 1000        = 20000ms ≙ 20s
      ➜ We expect our queue to be resolved after ~20s 
      ➜ For consistent enqueue() frequency we set msEnqueueMax to 20000
    

enter image description here (interactive chart: https://datawrapper.dwcdn.net/p4ZYx/2/ https://datawrapper.dwcdn.net/p4ZYx/2/)

我们看到了同样的趋势。wait ms随着时间的推移而增加(没什么新鲜的)。自从我们3queue.size底部的线被绘制到同一个图表中(Y 轴有ms规模),它们几乎看不见。快速切换到对数刻度以进行更好的比较:

enter image description here (interactive chart: https://datawrapper.dwcdn.net/lZngg/1/ https://datawrapper.dwcdn.net/lZngg/1/)

两条虚线为queue.size [on start] and queue.size [on end]几乎彼此重叠,一旦队列变空,最后就会下降到“0”。

queue.size [on add]看起来非常相似wait ms线。这就是我们所需要的。

   {queue.size [on add]} * X = {wait ms}
⇔ X = {wait ms} / {queue.size [on add]}

仅此一点在运行时对我们没有帮助,因为wait ms新排队任务未知(尚未运行)。所以我们还有 2 个未知变量:X and wait ms。我们需要另一种关系来帮助我们。

首先,我们打印新的口粮{wait ms} / {queue.size [on add]}进入图表(浅绿色)及其平均值(浅绿色水平虚线)。这非常接近20ms (avg. run ms我们的任务),对吗?

切换回linearY 轴并将其“最大比例”设置为80ms以便更好地了解它。 (暗示:wait ms现在超出​​了视口)

enter image description here (interactive chart: https://datawrapper.dwcdn.net/Tknnr/4/ https://datawrapper.dwcdn.net/Tknnr/4/)

回到我们任务的随机运行时间(点云)。我们仍然有“总平均值”20.72ms(深绿色水平虚线)。我们还可以计算之前任务的平均值在运行时(例如,任务 370 入队➜ 任务 [1,.., 269] = 平均运行时间的当前平均运行时间是多少)。但我们甚至可以更精确:

我们排队的任务越多,它们对总“平均运行时间”的影响就越小。因此,让我们计算一下“平均运行时间”last例如50tasks。这导致了一致的影响每个任务的“平均运行时间”为 1/50。 ➜ 峰值运行时间变得直线,并考虑趋势(向上/向下)。 (深绿色水平路径曲线紧邻我们的 1. 方程中的浅绿色)。

我们现在可以做的事情:

  1. We can 排除 X来自我们的第一个方程(浅绿色)。 ➜X可以用“之前的平均运行时间”来表示n例如50 个任务(深绿色)。 我们的新方程仅取决于在运行时、入队时已知的变量:

    // mean runtime from prev. n tasks:
    X = {[taskRun[-50], .. , taskRun[-2], taskRun[-1] ] / n } ms
    
    // .. replace X in 1st equation:
    ⇒ {wait ms} = {queue.size [on add]} * {[runtime[-50], .. , runtime[-2], runtime[-1] ] / n } ms
    
  2. 我们可以在图表中绘制一条新的图表曲线,并检查它与记录的相比有多接近wait ms(橙子)

enter image description here (interactive chart: https://datawrapper.dwcdn.net/LFp1d/2/ https://datawrapper.dwcdn.net/LFp1d/2/)

结论

我们可以预测wait考虑到我们的任务的运行时间可以通过某种方式确定,因此在任务入队之前。因此,它在将相同类型/功能的任务排队的情况下效果最好: 使用案例:一个AutoQueue实例充满了 UI 组件的渲染任务。渲染时间可能不会对聊天产生太大影响(与fetch())。也许您在地图上渲染 1000 个位置标记。每个标记都是一个类的实例,具有render() Fn.


Tips

  1. Queues用于各种任务。 ➜ 实施专用Queue不同类型逻辑的类变体(不要在一个类中混合不同的逻辑)
  2. 选择所有tasks that might被排队到相同的AutoQueue例如(现在或将来),它们可能会被阻止所有其他人.
  3. An AutoQueue不会提高运行时间,最多也不会降低。
  4. 使用不同的AutoQueue不同的实例Task types.
  5. Monitor the size of your AutoQueue, particular ..
    • ..在大量使用时(频繁使用enqueue())
    • .. on long or unknown task运行时间
  6. Check your error handling. Since errors inside your tasks will just reject their returned promise on enqueue (promise = queue.enqueue(..)) and will not stop the dequeue process. You can handle errors..
    • .. 在你的任务中 ➜ `try{..} catch(e){ .. }
    • ..就在它之后(在下一个之前)➜return new Promise()
    • ..“异步”➜queue.enqueue(..).catch(e => {..})
    • ..“全局”➜ 内部的错误处理程序AutoQueue class
  7. 取决于您的实施Queue你可能会看queue.size. An Array充满了 1000 个任务,其效率不如我在最终代码中使用的“双向链表”这样的去中心化数据结构。
  8. 避免递归地狱。 (使用就可以了tasks that enqueue()其他) - 但是,调试一个程序并不有趣AutoQueue where tasks是动态的enqueue()e 由其他人在async环境..
  9. 乍一看一个Queue可能会解决一个problem(在一定的抽象层次上)。然而,在大多数情况下,它会缩小现有的灵活性。它为我们的代码添加了一个额外的“控制层”(在大多数情况下,这是我们想要的),同时,我们签署了一份合同以接受严格的规则Queue。即使解决了问题,也可能不是最好的解决方案。

添加更多功能[基本]

  • 停止“自动dequeue()" on enqueue():自从我们的AutoQueue类是通用的,不限于长时间运行HTTP requests(),你可以enqueue()任何必须按顺序运行的函数,甚至3min运行函数,例如“存储模块的更新”,..您不能保证,当您enqueue()循环 100 个任务,上一个添加的任务尚未完成dequeued().

    您可能想阻止enqueue()从打电话dequeue()直到全部添加完毕。

    enqueue(action, autoDequeue = true) { // new
        return new Promise((resolve, reject) => {
            super.enqueue({ action, resolve, reject });
            if (autoDequeue) this.dequeue(); // new
        });
    }
    

    ..然后打电话queue.dequeue()在某个时刻手动。

  • 控制方法: stop / pause / start您可以添加更多控制方法。也许您的应用程序有多个模块,所有这些模块都试图fetch()页面加载时有资源。一个AutoQueue()工作原理就像Controller。您可以监视有多少任务正在“等待..”并添加更多控件:

    class AutoQueue extends Queue {
        constructor() {
            this._stop = false;  // new
            this._pause = false; // new
        }
    
        enqueue(action) { .. }
    
        async dequeue() {
            if (this._pendingPromise) return false;
            if (this._pause ) return false; // new
    
            if (this._stop) { // new
                this._queue = [];
                this._stop = false;
                return false;
            }
    
            let item = super.dequeue();
            ..
        }
    
        stop() { // new
            this._stop = true;
        }
    
        pause() { // new
            this._pause = true;
        }
    
        start() { // new
            this._stop = false;
            this._pause = false;
            return await this.dequeue();
        }
    }
    
  • 转发响应:您可能想要处理一个的“响应/值”task in the next任务。不保证我们的prev.任务还没有完成,当我们入队时2nd任务。 因此,最好存储上一个的响应。类中的任务并将其转发给下一个:this._payload = await item.action(this._payload)


错误处理

在 a 内抛出错误taskFn 拒绝返回的承诺enqueue()并且不会停止出队过程。您可能想在下一步之前处理错误task开始运行:

queue.enqueue(queue => myTask() ).catch({ .. }); // async error handling

queue.enqueue(queue  =>
    myTask()
        .then(payload=> otherTask(payload)) // .. inner task
        .catch(() => { .. }) // sync error handling
);

自从我们的Queue is dump,并且只是await为了我们的任务得到解决(item.action(this)),没有人阻止你返回 a new Promise()从当前运行的taskFn。 - 它将在下一个任务出队之前解决。

You can throw new Error()内部任务 Fn 并在“外部”/运行后处理它们:queue.enqueue(..).catch()。 您可以轻松地在内部添加自定义错误处理dequeue()调用的方法this.stop()清除 ”on hold“(排队的)任务..

您甚至可以从任务函数内部操作队列。查看:await item.action(this)调用与this并允许访问Queue实例。 (这是可选的)。有一些用例taskFn 应该不能。


添加更多功能[高级]

...达到文本限制:D

more: https://gist.github.com/exodus4d/6f02ed518c5a5494808366291ff1e206 https://gist.github.com/exodus4d/6f02ed518c5a5494808366291ff1e206


阅读更多

  • Blog: "带有回调、Promise 和异步的异步递归 https://blog.scottlogic.com/2017/09/14/asynchronous-recursion.html"
  • Book: "可调用值 https://exploringjs.com/impatient-js/ch_callables.html"
  • Book: "异步函数 https://exploringjs.com/es2016-es2017/ch_async-functions.html"
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JS 异步/等待任务队列 的相关文章

随机推荐

  • struct.error:解包需要 4 个字节的缓冲区

    我想将设备中的数据从咬合转换为浮动 我使用这个答案中的代码 浮动字节数 https stackoverflow com questions 5415 convert bytes to floating point numbers in py
  • Django 模板未加载

    我有一个名为 src 的项目和名为 app 的应用程序 并且我的应用程序中有一个模板文件夹 在我的模板文件夹中 我有另一个名为 Pages 的文件夹 我的 html 页面 base html 和 view html 驻留在其中 我的 vie
  • SwiftUI 文本视图不显示不可显示的字符

    我有 Swift 代码 可以解析 LLDP 链路层发现协议 帧 其中包括可以由供应商任意定义的字段 有时它是一个字符串 有时它是任意数据结构 以下是我如何获取数据并将其强制转换为具有 utf8 编码的字符串 let ouiString St
  • C# 读取USB描述符

    如何用C 读取USB描述符 我发现LibUSB NET但我无法像示例中那样获取 USB 设备 UsbRegDeviceList allDevices UsbDevice AllDevices 它返回计数 0 None
  • spring中使用@Bean和@Component的名称和别名

    我们如何定义 a 单一名字b 多个名称 别名 使用 Bean 和 Component 注释 在 XML 中我们这样定义 对于单个名称
  • 存储时间信息:需要时区吗?

    我很想知道我正在考虑的是否是不好的做法 或者因为这是一个特定且经过深思熟虑的选择 所以它实际上是一个不错的主意 我想存储特定城市发生的事件的日期信息 我想将该数据存储为 UTC 时间戳 简单地存储时间戳和城市 ID 国家 ID 与特定时区相
  • 在 TOleContainer 中嵌入多个 MS Word 实例

    这让我很头疼 我正在开发的 delphi 应用程序使用 TOLEContainer 中的 Word 打开一个或多个表单 我遇到的问题是 当使用嵌入的 Word 实例打开多个表单时 一个实例的控件会影响所有其他实例 而第一个实例上的控件不可用
  • CAKE 构建和 NUNIT3 生成空结果文件

    我正在使用 cake build 并尝试将 cake 单元测试结果上传到 AppVeyor 但是当我在本地运行时 Cake Nunit3 生成空结果 我认为这就是导致 AppVeyor 上错误的原因 在下面的块中 生成了 NUnitResu
  • 如何使用 Velocity.js 为翻译属性设置动画?

    我有一个简单的块 应该使用 translateX 向左移动 200px 它将向左移动 位置也向左 我似乎无法使用translateX 或translateY 移动块 Transform 翻译的 CSS 值将起作用 使用翻译的原因是与位置相比
  • 窗口最小化时的 AudioContext 计时问题

    我按照中的说明进行操作本文 http www html5rocks com en tutorials audio scheduling 并创建了一个 Javascript 节拍器 它利用 Web Audio API 并具有audioCont
  • Visual Studio 2010 右键单击​​时崩溃

    我在 Windows 7 中遇到了这个问题 现在使用 Windows 8 1 x64 的格式化后且非常干净的 C 磁盘 我也遇到了这个问题 当我在 Visual Studio 2010 中执行 Visual C 应用程序并右键单击以显示右侧
  • 如何在 OSX 上安装 python-gtk2、python-webkit 和 python-jswebkit

    我已经阅读了许多相关问题 但仍然不清楚如何做到这一点 因为有许多可用的软件组合 并且许多解决方案似乎已经过时 在 OSX 上的虚拟环境中安装以下内容的最佳方法是什么 python gtk2 python webkit python jswe
  • 没有父表标签的 td

    HTML td class tabletd text one td br td class tabletd this is next td td
  • ADK 1.0 设备无法与 Jelly Bean 配合使用,为什么?

    刚刚将久经考验的 ADK 配件连接到 Nexus 7 和 Galaxy Nexus 连接失败 并显示 无法读取设备协议版本 消息 以为主板坏了 用 ICS 连接到 Nexus S 工作得很好 在 Jelly Bean 升级之前也在 Gala
  • 消息:call_user_func_array() 期望参数 1 是有效的回调

    遇到 PHP 错误 严重性 警告 消息 call user func array 期望参数 1 是有效的回调 类 Error 没有方法 index 文件名 core CodeIgniter php 线路号码 532 回溯 文件 var ww
  • 在离子框架中放大时,离子滚动使我的图像变得模糊

    我在 ionic 框架的 ion scroll 标签中加载图片 当我尝试放大时 图片变得模糊 字母也无法辨认 这种情况在我的浏览器和 Android 上都会发生 我的模板的代码
  • 如何自定义android中长按/延迟按钮按下的时间间隔

    我正在制作一个应用程序 它有一个按钮来执行操作 但我想在用户长按按钮时执行该操作 因为 Google 提供了长按持续时间 appx 5 秒 但我想自定义这个持续时间 请帮忙 你可以试试Touch Listener去做这个 Try Handl
  • 用于组合 NUnit 或 MSTest XML 结果的 MSBuild 任务

    我有一组 NUnit XML 结果文件 由 xUnit NET 通过 Xunit Runner MSBuild xunit msbuild 任务的 NUnitXml 参数生成 几乎与如何让 Team Build 显示 xUnit net 测
  • 如何在 woocommerce 中为不同类别存档页面调用不同模板

    我在 woocommerce 中有两个类别 一个是 男性 另一个是 女性 默认 woocommerce 为类别存档页面调用 archive product php 但我想为每个类别存档页面显示不同的布局 这个怎么做 您可以看到 WooCom
  • JS 异步/等待任务队列

    在我的 JS 应用程序中 我使用 async await 功能 我想执行多个 API 调用 并希望它们被一个接一个地调用 换句话说 我想替换这个简单的方法 const addTask async url options gt return