JavaScript 同步承诺 Return

JavaScript Synchronized Promise Return

我有一个 JavaScript 方法来承诺一个对象。该对象应在第一次获取,但此后,应 returned 缓存实例。为了模拟检索,我们会在此处设置延迟。

var Promise = require('bluebird');
var retrievedObject = null;
var retrievalCount = 0;

var retrieveObject = Promise.coroutine(
function *retrieveObject(){
  if (retrievedObject) {
    return retrievedObject;
  }

  yield Promise.delay(1000); // wait one second
  retrievedObject = ++retrievalCount;

  return retrievedObject;
});

在这种情况下,为了简单起见,我们只等待一秒钟,然后 return 增加计数。如果缓存工作正常,计数应该只递增一次,而之后的所有时间,它应该保持相同的值,即 1。这应该满足我们的需求,作为递增代码是否已被递增的测试机制是否达到。

现在,我还有一个应该测试对象检索的函数,定义如下:

var testRetrievals = Promise.coroutine(
function *testRetrievals() {
  var count = yield retrieveObject();
  console.log(count);
});

多次调用该函数时,(像这样):

testRetrievals();
testRetrievals();
testRetrievals();
testRetrievals();
testRetrievals();

控制台日志应为:

1 1 1 1 1

然而,它真正读的是

1 2 3 4 5

原因很明显。 retrieveObject 方法在第一次调用的 promise 产生之前被调用,因为它需要不到一秒钟的时间来启动所有调用。但是,我正在寻找一种以某种方式同步 retrieveObject 方法的方法,以便在第一次调用之后,它不会进入检索过程(在我们的例子中是延迟),而是等待第一次调用完全的。最 JavaScript/Node-idiomatic 的方法是什么?我正在考虑引入类似信号量的结构,但我担心这会破坏 Node 的非等待行为。

retrievedObject = ++retrievalCount;您在此处的值上添加了 +1。如果您不想要这个结果,您应该将 retrievalCount 变量转换为本地变量或松散迭代...请参阅:

var retrieveObject = Promise.coroutine(
function *retrieveObject(){
   var retrievalCount = 0;

  if (retrievedObject) {
    return retrievedObject;
  }

  yield Promise.delay(1000); // wait one second
  retrievedObject = ++retrievalCount;

  return retrievedObject;
});

显然,promises 只能 resolve 或 reject 一次。这意味着如果我们用一个 promise 替换 promise 生成函数 retrieveObject ,我们只需在末尾添加一对括号,就像这样:

var retrieveObject = Promise.coroutine(
function *retrieveObject(){
  if (retrievedObject) {
    return retrievedObject;
  }

  yield Promise.delay(1000); // wait one second
  retrievedObject = ++retrievalCount;

  return retrievedObject;
})();

retrieveObject 将不再在调用时创建新的承诺,而是成为承诺本身。在它第一次解析后,所有后续的 "resolutions" 实际上都是对已缓存在引擎盖下的值的即时响应。这意味着我们可以完全放弃我们自己做的记忆:

var retrieveObject = Promise.coroutine(
function *retrieveObject(){
  yield Promise.delay(1000); // wait one second
  retrievedObject = ++retrievalCount;

  return retrievedObject;
})();

这意味着现在 testRetrievals 方法在生成 retrieveObject:

时必须去掉括号
var testRetrievals = Promise.coroutine(
function *testRetrievals() {
  var count = yield retrieveObject;
  console.log(count);
});

瞧,控制台输出如下所示:

1 1 1 1 1

然而,正如用户 Bergi 正确指出的那样,这将导致 promise 总是被调用(一次),即使它从未被需要。因此,一种解决方法是使 retrieveObject 函数再次成为承诺生成函数,如下所示:

var retrieveObject = Promise.coroutine(
function *retrieveObject(){
  yield Promise.delay(1000); // wait one second
  retrievedObject = ++retrievalCount;

  return retrievedObject;
});

而我们现在要做的是,在依赖于retrieveObjecttestRetrievals的函数中,我们在第一个运行:

期间记住promise
var retrieveObjectPromise = null;
var testRetrievals = Promise.coroutine(
function *testRetrievals() {
  if (!retrieveObjectPromise){
    retrieveObjectPromise = retrieveObject();
  }
  var count = yield retrieveObjectPromise;
  console.log(count);
});

I'm thinking of introducing semaphore-like structures

嗯,是的,你可以做到。我们需要做的主要事情是在调用方法时立即在我们的缓存中创建插槽,而不是在等待检索之后。

所以在要存储的 结果值 旁边,我们需要一个 pending 标志 来跟踪我们是否已经开始检索,以及一种方法通知听众在尚未 解决
时等待标志的更改 听起来很熟悉?对,那是一个 promise,它已经内置了这些信号量。

您只需将承诺本身存储在缓存中:

var Promise = require('bluebird');
var retrievedPromise = null;
var retrievalCount = 0;

var retrieveObject = Promise.coroutine(function* retrieveObject() {
    if (retrievedPromise) {
        return yield retrievedPromise;
    }

    retrievedPromise = Promise.delay(++retrievalCount, 1000); // wait one second
    var retrievedObject = yield retrievedPromise;
    return retrievedObject;
});

事实上,您甚至不需要带有 yield 的协程:

…
var retrievedPromise = null;
function retrieveObject() {
    if (!retrievedPromise) 
        retrievedPromise = Promise.delay(++retrievalCount, 1000); // wait one second
    return retrievedPromise;
}