Search code examples
javascriptpromisewhen-js

Cleaning up promises (flattening and error handling)


I'm using the when library and have some code like this:

when.join(
    database.then(function(db) {
        return db.collection("incidents");
    }).then(function(col) {
        return col.idExists(incidentId);
    }),
    database.then(function(db) {
        return db.collection("images");
    }),
    elib.uploadToS3(pic.path, 'image_uploads/' + id, pic.type)
).spread(function(exists, images, url) {
    if(!exists) {
        throw new Error("Incident id does not exist");
    }

    console.log("Image sucessfully uploaded to: ", url);
    return images.insert({
        _id: id,
        size: pic.size
    });
}).then(function() {
    console.log("At this point, it's totally succesfully recorded in the database!")
});

The code is reasonably readable, but the logic is:

  1. Ensure the incidentId is valid
  2. Get the image table
  3. Upload the image to S3

All these 3 can happen at the same time. Step 1 and 2 both share the same 'database.then', so I'd like to use that, but I don't know how to flatten promises.

If there's any problems (including an incidentId not being valid), I should call elib.deleteFromS3('image_uploads/' + id);

If that was all successful, I'm ready to "commit" by adding a new entry in the database: images.insert({ _id: id, size: pic.size })

If that works, we're done. If it doesn't, I still need to delete from S3 again.

Any help on keeping this readable while satisfying the error handling and 'database.then' reuse would be hugely appreciated.


Solution

  • Step 1 and 2 both share the same 'database.then', so I'd like to use that, but I don't know how to flatten promises.

    You're already reusing same database promise twice (and that's great), you're just after two different mappings of that promise, and it's very logical to use two different then calls in such case. Trying to do that with one, wouldn't be reasonable, and clearly won't give you any benefit.

    I also wouldn't mess with S3 until I'm sure there's reason for operation. So I would do 1 and proceed with 2 & 3 only after id exists:

    database.then(function(db) {
      return db.collection("incidents");
    }).then(function(col) {
      return col.idExists(incidentId);
    }).then(function (exists) {
      if (!exists) throw new Error("Incident id does not exist");
      return when.join(
        database.then(function(db) {
          return db.collection("images");
        }),
        elib.uploadToS3(pic.path, 'image_uploads/' + id, pic.type)
      ).spread(function(images, url) {
        console.log("Image sucessfully uploaded to: ", url);
        return images.insert({
          _id: id,
          size: pic.size
        })(null, function (err) {
          return elib.deleteFromS3('image_uploads/' + id).then(function () {
           throw err;
          });
        });
    }).then(function() {
      console.log("At this point, it's totally succesfully recorded in the database!")
    });