如何在JS / TS中实现伪阻塞异步队列?

时间:2017-11-07 11:58:36

标签: javascript typescript asynchronous promise async-await

所以这是一个矛盾的说法:我想在javascript / typescript中创建一个异步阻塞队列(如果你可以在没有打字稿的情况下实现它,那很好)。基本上我想实现类似Java BlockingQueue期望的东西,而不是它实际上是阻塞的,它会是异步的,我可以等待出列。

这是我想要实现的界面:

interface AsyncBlockingQueue<T> {
  enqueue(t: T): void;
  dequeue(): Promise<T>;
}

我会像这样使用它:

// enqueue stuff somewhere else

async function useBlockingQueue() {
  // as soon as something is enqueued, the promise will be resolved:
  const value = await asyncBlockingQueue.dequeue();
  // this will cause it to await for a second value
  const secondValue = await asyncBlockingQueue.dequeue();
}

有什么想法吗?

2 个答案:

答案 0 :(得分:7)

实际上非常简单,dequeue将创建enqueue将解决的承诺。我们只需要将解析器保留在一个队列中 - 并且还要关心值在队列出列之前入队的情况,并将已经完成的promise保留在队列中。

class AsyncBlockingQueue {
  constructor() {
    // invariant: at least one of the arrays is empty
    this.resolvers = [];
    this.promises = [];
  }
  _add() {
    this.promises.push(new Promise(resolve => {
      this.resolvers.push(resolve);
    });
  }
  enqueue(t) {
    // if (this.resolvers.length) this.resolvers.shift()(t);
    // else this.promises.push(Promise.resolve(t));
    if (!this.resolvers.length) this._add();
    this.resolvers.shift()(t);
  }
  dequeue() {
    if (!this.promises.length) this._add();
    return this.promises.shift();
  }
  // now some utilities:
  isEmpty() { // there are no values available
    return !this.promises.length; // this.length == 0
  }
  isBlocked() { // it's waiting for values
    return !!this.resolvers.length; // this.length < 0
  }
  get length() {
    return this.promises.length - this.resolvers.length;
  }
}

我不知道TypeScript,但可能只是添加必要的类型注释很简单。

为了获得更好的性能,请使用带有循环缓冲区而不是普通数组的Queue实现,例如: this one。您也可以只使用一个队列,并记住您当前是否存储承诺或解析器。

答案 1 :(得分:3)

这只是@ Bergi的答案,但是使用了一些修改后的打字稿+泛型,以便我可以在严格的模式下输入兄弟姐妹。

class AsyncBlockingQueue<T> {
  private _promises: Promise<T>[];
  private _resolvers: ((t: T) => void)[];

  constructor() {
    this._resolvers = [];
    this._promises = [];
  }

  private _add() {
    this._promises.push(new Promise(resolve => {
      this._resolvers.push(resolve);
    }));
  }

  enqueue(t: T) {
    if (!this._resolvers.length) this._add();
    const resolve = this._resolvers.shift();
    if (!resolve) {
      // can never happen
      throw new Error('resolve function was null or undefined when attempting to enqueue.')
    };
    resolve(t);
  }

  dequeue() {
    if (!this._promises.length) this._add();
    const promise = this._promises.shift();
    if (!promise) {
      // can never happen
      throw new Error('promise was null or undefined when attempting to dequeue.');
    }
    return promise;
  }

  isEmpty() {
    return !this._promises.length;
  }

  isBlocked() {
    return !!this._resolvers.length;
  }

  get length() {
    return this._promises.length - this._resolvers.length;
  }
}