你能在不使用 Deferred 的情况下写这个吗?

Can you write this without using a Deferred?

我在下面写了一些使用 promises 的代码,我能找到的最简单的编写方法是使用 Deferred 对象而不是通常的 Promise 执行器函数,因为我需要从执行器外部解析 promise .我想知道是否有一个基于 Promise 执行程序函数的可接受的设计模式来解决这样一个不使用类似延迟的解决方案的问题?可以在不必从承诺执行者外部解决承诺的情况下完成吗?

这是详细信息。

我有一个项目使用了一组工作线程,并且代码的各个部分不时要使用工作线程。为了管理它,我创建了一个简单的 WorkerList class 来保存可用工作线程的列表。当有人想要使用一个时,他们会调用 get() 并且 returns 一个解析为工作线程的承诺。如果工作线程立即可用,则承诺立即解决。如果所有工作线程都在使用中(因此可用工作线程列表为空),则承诺不会解决,直到稍后通过 add(worker) 方法将一个工作线程放回可用列表中。

这个WorkerList class只有两个方法,add(worker)get()。你 get() 一个工人,当你完成它时,你 add(worker) 它回来了。当你 add(worker) 它返回时,class 检查是否有任何任务在等待可用的 Worker。如果有,它会解决他们与可用 Worker 的承诺。解决别人的承诺就是使用 Deferred 的地方。

这是 WorkerList 的代码:

class WorkerList {
    constructor() {
        this.workers = [];
        this.deferredQueue = [];
    }
    add(worker) {
        this.workers.push(worker);

        // if someone is waiting for a worker,
        // pull the oldest worker out of the list and
        // give it to the oldest deferred that is waiting
        while (this.deferredQueue.length && this.workers.length) {
            let d = this.deferredQueue.shift();
            d.resolve(this.workers.shift());
        }
    }
    // if there's a worker, get one immediately
    // if not, return a promise that resolves with a worker
    //    when next one is available
    get() {
        if (this.workers.length) {
            return Promise.resolve(this.workers.shift());
        } else {
            let d = new Deferred();
            this.deferredQueue.push(d);
            return d.promise;
        }
    }
}

并且,这是延迟实现:

function Deferred() {
    if (!(this instanceof Deferred)) {
        return new Deferred();
    }
    const p = this.promise = new Promise((resolve, reject) => {
        this.resolve = resolve;
        this.reject = reject;
    });
    this.then = p.then.bind(p);
    this.catch = p.catch.bind(p);
    if (p.finally) {
        this.finally = p.finally.bind(p);
    }
}

也许下面只是一个可怜人对延迟的处理方法,并没有真正触及问题的症结所在,但是你可以保留一个解析器函数队列,而不是一个延迟队列。

这比您的方法节省了少量代码,并避免显式使用 Deferreds。

我不知道是否有针对此的既定模式,但这本身似乎是一种用于维护异步对象池的可重用模式,因此与其调用它 WorkerList,不如命名它 AsyncPool,然后将其组合为 WorkerList:

中的可重复使用的部分

class AsyncPool {
    constructor() {
        this.entries = [];
        this.resolverQueue = [];
    }
    add(entry) {
        console.log(`adding ${entry}`);
        this.entries.push(entry);

        // if someone is waiting for an entry,
        // pull the oldest one out of the list and
        // give it to the oldest resolver that is waiting
        while (this.resolverQueue.length && this.entries .length) {
            let r = this.resolverQueue.shift();
            r(this.entries.shift());
        }
    }
    // if there's an entry, get one immediately
    // if not, return a promise that resolves with an entry
    //    when next one is available
    get() {
        return new Promise((r) => 
            this.entries.length
                ? r(this.entries.shift())
                : this.resolverQueue.push(r)
        );
    }
}


let pool = new AsyncPool();

pool.add('Doc');
pool.add('Grumpy');
pool.get().then(console.log);
pool.get().then(console.log);
pool.get().then(console.log);
pool.get().then(console.log);

// add more entries later
setTimeout(() => pool.add('Sneezy'), 1000);
setTimeout(() => pool.add('Sleepy'), 2000);

这是一种解决方案,它不会在承诺执行程序函数之外的任何地方公开承诺解析器函数。

根据我对自己关于基于事件的解决方案的问题的评论,这就是我想出的。它使用触发事件和事件侦听器在 promise 执行器函数内引发操作。

class WorkerList extends EventEmitter {
    constructor() {
        this.workers = [];
    }
    add(worker) {
        this.workers.push(worker);
        // notify listeners that there's a new worker in town
        this.emit('workerAdded');
    }
    // if there's a worker, get one immediately
    // if not, return a promise that resolves with a worker
    //    when next one is available
    get() {
        if (this.workers.length) {
            return Promise.resolve(this.workers.shift());
        } else {
            return new Promise(resolve => {
                const onAdded = () => {
                    if (this.workers.length) {
                        this.off('workerAdded', onAdded);
                        resolve(this.workers.shift());
                    }
                }

                this.on('workerAdded', onAdded);
            });
        }
    }
}

我最初担心维护 FIFO 顺序,以便第一个调用 get() 的人获得下一个可用的工作人员。但是,因为 eventListeners 是按照添加的顺序调用的,我认为这实际上会实现 FIFO 顺序。如果多次调用 get(),他们都会收到关于 workerAdded 的通知,但是在第一个处理消息并获取 worker 之后,其他人会发现没有剩下的 worker,所以他们的当有工作人员为他们服务时(当他们的听众排在第一位时),听众将继续等待未来的 workerAdded 消息。

我认为我不一定比显示的其他选项更喜欢这个,但它是一个替代方案并且不使用 Deferreds 甚至不在执行程序函数之外公开 resolve 处理程序。


正如所建议的那样,这也可以在 eventEmitter 是实例变量而不是基数的情况下完成 class:

class WorkerList {
    constructor() {
        this.workers = [];
        this.emitter = new EventEmitter();
    }
    add(worker) {
        this.workers.push(worker);
        // notify listeners that there's a new worker in town
        this.emitter.emit('workerAdded');
    }
    // if there's a worker, get one immediately
    // if not, return a promise that resolves with a worker
    //    when next one is available
    get() {
        if (this.workers.length) {
            return Promise.resolve(this.workers.shift());
        } else {
            return new Promise(resolve => {
                const onAdded = () => {
                    if (this.workers.length) {
                        this.emitter.off('workerAdded', onAdded);
                        resolve(this.workers.shift());
                    }
                }

                this.emitter.on('workerAdded', onAdded);
            });
        }
    }
}