1

Coming from a Java background I am now trying to wrap my mind around the asynchronous nature of Javascript. I use promises in my code to do this and until now everything works like a charm, but now I am having a conceptual question and didn't find a definitive answer even after reading the Promise/A+ spec multiple times.

My requirements are this: I have a method that modifies a shared object, stores the update in a PouchDB and reads it back afterwards in order to get an updated revision id field from the db (optimistic locking). Storing and updating the data in Pouch is asynchronous (I am omitting storing "this" to call the methods from within the promises for brevity):

var _doc = ...;
var _pouch = new PouchDB(...);

function setValue(key, value) {
    _doc[key] = value;
    _pouch.put(_doc)
    .then(function() {
        return _pouch.get(_doc._id);
    })
    .then(function(updatedDoc) {
        _doc = updatedDoc;
    });
}

Now, I want to make sure that no other key is set on _doc while it is being written to the db before it has been read again. Is it (a) even possible that another setValue() call is executing a put() (with an outdated revision id) while the get() call from Pouch has not been executed (given the message-queue-approach that JS is using) and (b) if it is possible, is the following solution fail-safe (it is working in my tests, but since I don't know if my tests are considering all possibilities...; storing "this" is again omitted):

var _doc = ...;
var _pouch = new PouchDB(...);
var _updatePromise;

function setValue(key, value) {
    if (_updatePromise == null) {
        setValueInternal(key, value);
    }
    else {
        // make sure the previous setValue() call is executed completely before
        // starting another one...
        _updatePromise.then(function() {
            setValueInternal(key, value);
        });
    }
}

function setValueInternal(key, value) {
    _doc[key] = value;

    _updatePromise = new Promise(function(done, reject) {
        _pouch.put(_doc)
        .then(function() {
            return _pouch.get(_doc._id);
        })
        .then(function(updatedDoc) {
            _doc = updatedDoc;
            _updatePromise = null;
            done();
        })
        catch(function(error) {
            _updatePromise = null;
            reject(error);
        });
    });
}

I think it should work correctly if fulfilling a promise (calling done()) will synchronously call the next then() function, but I am unable to find a definitive answer whether this is the case.

Any clarification is greatly appreciated and thanks for your help.

4
  • 1
    I've got news for you. Promises don't help you write atomic code one bit. In fact, they may even make things worse because every .then() handler is guaranteed to be async which means other things can execute before a .then() handler is called. It appears your understanding here is way off base and your design logic is just going the wrong direction. Commented May 4, 2016 at 17:22
  • 1
    The only way to truly get an atomic revision id after updating a value is to have the database return the revision id from the write call (putting the responsibility for the atomic operation on the DB itself where it can be safely implemented). Assuming this is a multi-user database, anything else allows an opportunity for other changes to happen to the database before you read back the revision id. If you're just trying to protect from your own code writing something else to the DB (not multi-users), then you will have to implement some sort of flag which all of your code checks. Commented May 4, 2016 at 17:47
  • @jfriend00 I know that .then() is executed asynchronously, that's why I want to wait for the last then() (the "return" of the put()) to finish before I resolve _updatePromise as a whole. The DB is not multi-user in this case, because it is a local PouchDB. I just wanted to make sure that setValue() executions are running in a sequential way. It is perfectly ok that it is called multiple times as long as the operations in it are queued to execute in order. Commented May 5, 2016 at 6:41
  • @mrmcgreg - I am not sure that this is correct, because the promise is fulfilled when I call done(), not earlier, isn't it, so returning something from within the constructor of the Promise won't do any good to me. Perhaps you can check my code again, I am not inside a then() when I do the put() (see Dark Falcon's response below). This is of course a simplified example, the real code extracted the update/retrieve code into a separate method that other methods call. Commented May 5, 2016 at 6:44

1 Answer 1

1

Chaining promises as you're attempting to do here does indeed work as expected, but I do not believe there is any guarantee that done is called synchronously. I think your code would work, but you have some anti-patterns in it. I would recommend simplifying to avoid explicit creation of the promises.

Also think about: If you call setValue 4 times in a row, how many round-trips to the server should that make? Doing it this way is going to make it take 4. Did you want to batch them into 1 or 2?

One Round Trip Per setValue:

var _doc = ...;
var _pouch = new PouchDB(...);
var _updatePromise = Promise.resolve();

function setValue(key, value) {
    // make sure the previous setValue() call is executed completely before
    // starting another one...
    _updatePromise = _updatePromise.then(function() {
        _doc[key] = value;

        return _pouch.put(_doc)
        .then(function() {
            return _pouch.get(_doc._id);
        })
        .then(function(updatedDoc) {
            _doc = updatedDoc;
        });
    });
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, first of all for pointing out the anti-pattern, I changed this already and it looks much nicer. As for multiple updates - I also have a bulk method that allows to change multiple things at a time before doing the update and this is a local PouchDB with no sync to a remote endpoint, so there are no roundtrips involved.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.