The problem

There is a requirement to implement an asynchronous task queue and process all the tasks in the queue in sequence as follows:

  1. Add asynchronous tasks to the queue at random time
  2. Tasks in the queue are executed on a first-in, first-out basis
  3. Tasks are asynchronous requests and wait for one to complete before executing the next

This requirement is easy to implement using BlockingQueue in the Java language, but not so easy because JavaScript has no locking mechanism.

Plan a

It is easy to think of a synchronous non-blocking scheme that checks the queue at regular intervals to see if there are any tasks in the queue and then removes the first one. Here the detection interval is 500 milliseconds, using setTimeout to simulate an asynchronous request.

<body>
  <button onclick="clickMe()">Am I</button>
</body>
Copy the code
let queue = []
let index = 0
function clickMe() {
  queue.push({name: 'click'.index: index++})
}

run()
async function run() {
  while (true) {
    if (queue.length > 0) {
      let obj = queue.shift()
      let res = await request(obj.index)
      console.log('Processed Event' + res)
    } else {
      await wait(500)
      console.log('----- queue idle -----')}}}// Simulate an asynchronous request with setTimeout
function request(index) {
  return new Promise(function (resolve, reject) {
    setTimeout(() = > {
      resolve(index)
    }, 1000)})}function wait(time) {
  return new Promise(function (resolve) {
    setTimeout(() = > {
      resolve()
    }, time)
  })
}
Copy the code

But there are two problems with this scheme.

  • Idle queues are still being recycled, consuming resources
  • The detection interval is difficult to grasp. If the detection interval is too large, queue tasks cannot be processed, and the detection interval is too small, resources are consumed

Is there a way to block if a queue is idle, like BlockingQueue in Java?

Scheme 2

Main ideas:

  • The asynchronous request is queued. When the number of tasks in the queue is greater than 0, the task in the queue is processed
  • Wait until one task is complete before performing the next
  • After all tasks in the queue are processed, the running status is false
<body>
<button onclick="clickMe()">Am I</button>
</body>
Copy the code
// Asynchronous request queue
const queue = []
// To simulate different return values
let index = 0
// Indicates whether a request in the queue is being processed
let running = false

// Use setTimeout to simulate an asynchronous request
function request(index) {
  return new Promise(function (resolve) {
    setTimeout(() = > {
      resolve(index)
    }, 1000)})}// Click continuously to trigger asynchronous request and join the task queue
function clickMe() {
  addQueue(() = > request(index++))
}

// When the number of tasks in the queue is greater than 0, the queue starts to process tasks
function addQueue(item) {
  queue.push(item)
  if (queue.length > 0 && !running) {
    running = true
    process()
  }
}

function process() {
  const item = queue.shift()
  if (item) {
    item().then(res= > {
      console.log('Processed Event' + res)
      process()
    })
  } else {
    running = false}}Copy the code

conclusion

Using the Promise feature that resolve does not always block, you can implement a Java-like BlockingQueue, where asynchronous tasks are executed in sequence and the queue is idle without consuming resources.