1

When I use Google Cloud services in NodeJS I have to access resources like Buckets (in Storage) and the first time I call them, I have to check if they exists, if not, create them.

Well, if multiple resources access the same bucket at the start of the application, they will try to create it at same time.

To prevent the stampede I have to use something like this:

getStorage(id) {
    return new Promise((resolve, reject) => {

        // Exists storage?
        if(id in this.storage) {
            let storage = this.storage[id];

            // Storage is ready, deliver
            if(storage.ready) {
                return resolve(storage);
            }

            // Not ready, wait until storage is ready
            let wait;
            let start = +Date.now();

            wait = setInterval(() => {
                // Storage is now ready
                if(storage.ready) {
                    clearInterval(wait);
                    return resolve(storage);
                }

                // Timeout in 15 seconds
                if(+Date.now() - start > 15*1000) {
                    clearInterval(wait);
                    return reject(new Error('timeout while waiting storage creation'));
                }
            }, 10);

            return;
        }

        // Start storage creation
        let storage = new Storage(id);

        this.storage[id] = storage;

        storage
            .create()
            .then(resolve, reject);
    });
}

Is anything in lodash, underscore or async that helps with this scenario? A instance stampede scenario.

Semaphores will be useful?

In async style it could be something like this:

getStorage(id) {
    return new Promise((resolve, reject) => {
        // already found
        if(id in this.storage) {
            resolve(this.storage[id]);
            return;
        }

        // prevent stampede

        // async.stampede( UNIQUE_ID, CREATE, RESULT )
        // - CREATE will be called once for UNIQUE_ID

        async.stampede(id, (end) => {
            let storage = new Storage(id);

            storage
                .create()
                .then(
                    () => end(null, storage),
                    (err) => end(err)
                );

        }, (err, result) => {
            if(err) reject(err);
            else {
                this.storage[id] = storage;
                resolve(result);
            }
        });
    });
}

In lodash it can be something like "once" but with promises.

Wiliam
  • 3,714
  • 7
  • 36
  • 56
  • Not sure what this `async` is that you refer to, I can't find any `stampede` method? – Bergi Dec 11 '16 at 12:25
  • @Bergi doesn't exists, I was trying to imagine how an async method for stampedes could be. I'm going to clarify that in the question, sorry! – Wiliam Dec 11 '16 at 13:27

1 Answers1

2

You don't need any semaphores or special treatment for promises. Promises are values representing the asynchronous result, and you can trivially cache them directly. You don't want "something like" once, you want exactly once. You don't need anything more.

Or actually, since you want to cache them based on the id parameter, you'll need to use memoize from Underscore/Lodash:

getStorage: _.memoize(id => new Storage(id).create())

or written without the library,

getStorage(id) {
    // Exists storage?
    if (id in this.storage) {
        return this.storage[id]; // it's a promise
    } else {
        // Start storage creation
        let storage = new Storage(id);
        let promise = storage.create();
        this.storage[id] = promise; // store the promise!
        return promise;
    }
}

Make sure to avoid the Promise constructor antipattern!

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I hope I didn't hurt your feelings by converting this into a oneliner :-) – Bergi Dec 11 '16 at 12:35
  • didn't hurt haha! I'm going to check the antipattern and try with once. Actually the last piece of code (without library) will not prevent "create" stampede right? In the create, node will try to connect to Google cloud several times for the same ID, I want only one to try to create it (or check if exists) and the other ones to wait. – Wiliam Dec 11 '16 at 13:17
  • 1
    No, the `this.storage[id]` is filled immediately (with the promise, not the result), and any subsequent calls will simply return that and not call `create` a second time. – Bergi Dec 11 '16 at 14:38
  • right, promise is only instanced once when calling multiple times. The error was instance multiple promises. Thanks :) – Wiliam Dec 12 '16 at 07:32