JavaScript async functions queue approach
One thing I find problematic is that queue.next is visible and can openly be called. The problem arises if your async function is waiting, and something else suddenly shifts the queue. You would get an unexpected results. To remedy this, I would suggest hiding next so that only the callbacks are aware of it.

One way to do it is pass in next as an argument to the callback. next would be a function that simply calls the next callback. If I remember correctly, this is how express did middleware.

function callback(arg1, arg2, ..., next){
  // do stuff
  next(arg1, arg2, ...);
}


An alternative would be to return a Promise. An advantage with this approach is that you remove the complexity of having to wrap the next callback as the next argument and instead, just call the callback and expect a Promise.

function callback(resolvedValue){
  // do stuff
  return Promise.resolve(resolution);
}


For your error, you should not rely on try-catch. try-catch should only be used for unexpected errors or errors that are intentionally thrown (like from an API) and meant to be recovered. But in this case, you are aware that it will throw an error when the queue is empty. You should do the check if the queue is empty before proceeding instead of relying on try-catch.

You should consider hiding the queue array. The problem with it being in the open is that it can be modified freely. It might be altered unintentionally, causing odd results. Consider hiding it inside a closure.

Your queue should hold a flag indicating that it's running or paused. Your queue could be in the middle of an async operation when queue.start is called. You wouldn't want the queue to suddenly start running. Also, if your queue runs out of callbacks, what does it do? Does it pause? Does it pick up after receiving a callback? How does third-party code know if the queue is still processing? These are some things to consider, and it depends on how the queue is used.