JavaScript同步承诺返回

时间:2015-12-10 19:25:41

标签: javascript node.js synchronization promise semaphore

我有一个承诺对象的JavaScript方法。该对象应该在第一次获取,但此后,应该返回一个缓存的实例。为了模拟检索,我们会在这里加上延迟。

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;
});

在这种情况下,为简单起见,我们只需等待一秒钟然后返回增加的计数。如果缓存正常工作,计数应该只增加一次,而在之后的所有时间,它应该保持相同的值,这是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

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

3 个答案:

答案 0 :(得分:1)

显然,承诺只能解决或拒绝一次。这意味着如果我们用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在调用时将不再创建新的承诺,而是承诺本身。在它第一次结算之后,所有后续的“决议”实际上都是对已经缓存的价值的瞬时响应。这意味着我们可以完全放弃我们自己做的备忘录:

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正确地指出的那样,这将导致承诺总是被调用(一次),即使它从未被要求过。因此,一种解决方法是使retrieveObject函数再次成为承诺生成函数,如下所示:

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

  return retrievedObject;
});

我们现在所做的是,在依赖retrieveObjecttestRetrievals的函数中,我们在第一次运行时记住了这个承诺:

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

答案 1 :(得分:1)

  

我正在考虑引入类似信号量的结构

是的,你可以这样做。我们需要做的主要是在调用方法时在缓存中创建槽,而不是在等待检索之后。

因此,要存储的结果值旁边,我们需要一个跟踪我们是否已经开始检索的挂起标志,以及一种通知等待更改的侦听器的方法虽然它尚未已落定 ...的旗帜 听起来很熟悉?是的,这是一个承诺,它已经内置了这些信号量。

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

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 s的协程:

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

答案 2 :(得分:0)

retrieveObject = ++ 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;
});