Tengo un método de JavaScript que promete un objeto. Ese objeto debe recuperarse la primera vez, pero a partir de entonces, debe devolverse una instancia en caché. Para simular la recuperación, pondremos un retraso aquí.
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;
});
En este caso, por simplicidad, solo esperamos un segundo y luego devolvemos un conteo incrementado. Si el almacenamiento en caché funciona correctamente, el recuento debe incrementarse solo una vez, mientras que todas las veces posteriores, debe mantenerse en el mismo valor, que es 1. Esto debería servir a nuestras necesidades como un mecanismo de prueba de si el código de incremento ha sido o no alcanzado o no.
Ahora, también tengo una función que se supone que prueba la recuperación del objeto, definida así:
var testRetrievals = Promise.coroutine(
function *testRetrievals() {
var count = yield retrieveObject();
console.log(count);
});
Al llamar a esa función varias veces, (así):
testRetrievals();
testRetrievals();
testRetrievals();
testRetrievals();
testRetrievals();
El registro de la consola debería leer:
1 1 1 1 1
Lo que realmente lee, sin embargo, es
1 2 3 4 5
La razón de eso es obvia. El método retrieveObject
se llama antes de que la promesa de la primera llamada rinda porque lleva menos de un segundo que se inicien todas las llamadas. Sin embargo, estoy buscando una manera de sincronizar de alguna manera el método retrieveObject
de modo que después de la primera llamada, no entre en el procedimiento de recuperación (el retraso en nuestro caso), pero espere la primera llamada para completar. ¿Cuál sería la forma más idiomática de JavaScript / Node de hacer eso? Estoy pensando en introducir estructuras parecidas a semáforos, pero me preocupa que eso rompa el comportamiento de Node que no espera.
3 respuestas
Estoy pensando en introducir estructuras parecidas a semáforos.
Pues sí, podrías hacer eso. Lo principal que debemos hacer es crear la ranura en nuestro caché justo cuando se llama al método, no después de esperar la recuperación.
Entonces, junto al valor del resultado para almacenar, necesitaríamos una bandera pendiente que rastrea si ya comenzamos a recuperar, y una forma de notificar a los oyentes que esperan el cambio de la bandera mientras aún no está resuelto ...
¿Suena familiar? Bien, esa es una promesa , tiene exactamente estos semáforos ya integrados.
Simplemente guarda la promesa en el caché:
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;
});
De hecho, ni siquiera necesita una rutina con yield
s para eso:
…
var retrievedPromise = null;
function retrieveObject() {
if (!retrievedPromise)
retrievedPromise = Promise.delay(++retrievalCount, 1000); // wait one second
return retrievedPromise;
}
RetrievedObject = ++ retrievalCount; está agregando +1 en el valor aquí mismo ... si no desea este resultado, debe convertir la variable retrievalCount en local o perder la iteración ... Consulte:
var retrieveObject = Promise.coroutine(
function *retrieveObject(){
var retrievalCount = 0;
if (retrievedObject) {
return retrievedObject;
}
yield Promise.delay(1000); // wait one second
retrievedObject = ++retrievalCount;
return retrievedObject;
});
Aparentemente, las promesas solo pueden resolverse o rechazarse una vez. Eso significa que si reemplazamos la función generadora de promesas retrieveObject
con una promesa, lo que hacemos simplemente agregando un par de paréntesis al final, de esta manera:
var retrieveObject = Promise.coroutine(
function *retrieveObject(){
if (retrievedObject) {
return retrievedObject;
}
yield Promise.delay(1000); // wait one second
retrievedObject = ++retrievalCount;
return retrievedObject;
})();
retrieveObject
ya no creará nuevas promesas cuando se llame, sino que será una promesa en sí misma. Después de que se resuelve por primera vez, todas las "resoluciones" posteriores serán, de hecho, respuestas instantáneas del valor que se ha almacenado en la memoria caché. Lo que significa que podemos descartar por completo la memorización que hacemos nosotros mismos:
var retrieveObject = Promise.coroutine(
function *retrieveObject(){
yield Promise.delay(1000); // wait one second
retrievedObject = ++retrievalCount;
return retrievedObject;
})();
Eso significa que ahora, el método testRetrievals
tiene que perder los paréntesis al generar retrieveObject:
var testRetrievals = Promise.coroutine(
function *testRetrievals() {
var count = yield retrieveObject;
console.log(count);
});
Y voilá, la salida de la consola se ve así:
1 1 1 1 1
Sin embargo, como señaló acertadamente el usuario Bergi, eso daría lugar a que la promesa siempre se llame (una vez), incluso si nunca fue requerida. Entonces, una solución alternativa sería hacer que la función retrieveObject
vuelva a ser una promesa, como esta:
var retrieveObject = Promise.coroutine(
function *retrieveObject(){
yield Promise.delay(1000); // wait one second
retrievedObject = ++retrievalCount;
return retrievedObject;
});
Y lo que hacemos ahora es que, en la función que se basa en retrieveObject
, testRetrievals
, recordamos la promesa durante la primera ejecución:
var retrieveObjectPromise = null;
var testRetrievals = Promise.coroutine(
function *testRetrievals() {
if (!retrieveObjectPromise){
retrieveObjectPromise = retrieveObject();
}
var count = yield retrieveObjectPromise;
console.log(count);
});
Preguntas relacionadas
Nuevas preguntas
javascript
Para preguntas relacionadas con la programación en ECMAScript (JavaScript / JS) y sus diversos dialectos / implementaciones (excluyendo ActionScript). Esta etiqueta rara vez se usa sola, pero a menudo se asocia con las etiquetas [node.js], [json] y [html].