"Translation" Promise Anti-Patterns

✍🏼 Written on Mar 4, 2016   
❗️ Note: it has been days since this article was written, please be aware of its timeliness

Recently, while studying topics related to Promise, I came across this article and found it quite insightful, so I decided to document it.

Promises itself is quite simple—provided you can find the right approach. Below are a few confusing aspects of Promises to test whether you’ve truly mastered Promise. Some of these once drove me crazy.

Nested Promises

You have a bunch of Promises nested within each other:

1
2
3
4
5
loadSomething().then(function (something) {
loadAnotherthing().then(function (another) {
DoSomethingOnThem(something, another);
});
});

The reason you’re doing this is that you need to handle the results of both Promises, so you can’t chain them because the then() method only accepts the result returned by the previous then(). (This means these two Promise need to be processed simultaneously without any sequential dependency, whereas then implies a sequential relationship. If the former throw error, it directly enters the catch handling phase.)

Hah, the real reason you wrote it this way is that you didn’t know about the all() method:

Here’s the solution to this ugly approach:

1
2
3
4
5
6
q.all([loadSomething(), loadAnotherThing()]).spread(function (
something,
another
) {
DoSomethingOnThem(something, another);
});

Much cleaner. q.all() returns a promise object, combines the results into an array, and passes it to the resolve method for subsequent then method calls. The spread() method will split this array into arguments of the same length and pass them to the DoSomethingOnThem function.

(Note: Promise.all() accepts an array as a parameter, where the elements are promise and have no sequential dependency. They execute simultaneously, and the final value passed to the then method is an array of the return values from each promise method. Here, the author uses a module q from node as an example.)

Broken Promise Chain

Suppose you have code like this:

1
2
3
4
5
6
7
function anAsyncCall() {
var promise = doSomethingAsync();
promise.then(function () {
somethingComplicated();
});
return promise;
}

The problem with this code is that any errors occurring in the somethingComplicated() function’s error won’t be caught. Promises implies the ability to chain calls (otherwise, why call it then? Just use done directly). Each called then() method returns a new promise, and this new promise will be processed by the next then() method. Normally, the last call should be the catch() method, and any error occurring anywhere in the chain will be caught and handled by it.

In the above code, the chain breaks when you return the first promise instead of returning a new promise processed by then for the final then call (i.e., then doesn’t modify the original promise; it only processes it and returns a new promise).

The solution to this problem:

1
2
3
4
5
6
function anAsyncCall() {
var promise = doSomethingAsync();
return promise.then(function () {
somethingComplicated();
});
}

Remember: always return the result of the last then() (to enable chaining).

Chaotic Collection

You have an array of elements, and you want to perform some asynchronous operation on each element. So you find yourself needing to do something involving recursive calls.

1
2
3
4
5
6
7
8
9
10
11
function workMyCollection(arr) {
var resultArr = [];
function _recursive(idx) {
if (idx >= resultArr.length) return resultArr;
return doSomethingAsync(arr[idx]).then(function (res) {
resultArr.push(res);
return _recursive(idx + 1);
});
}
return _recursive(0);
}

Well… this code isn’t very intuitive. The crux of the problem is that chaining becomes painful when you don’t know how long the chain will be—unless you know about (JavaScript ES5+’s native array methods) map() and reduce().

Solution:
Remember, the q.all parameter is an array of promise, and it will place the results into an array and pass them to the resolve method. We can simply use the array’s map method to perform this asynchronous operation on each element, like this:

1
2
3
4
5
6
7
function workMyCollection(arr) {
return q.all(
arr.map(function (item) {
return doSomethingAsync(item);
})
);
}

Unlike the initial recursive approach, which wasn’t really a solution, this code synchronously passes each array element to an asynchronous function. It’s clearly more time-efficient.

If you need to return promises in order, you can use reduce:

1
2
3
4
5
6
7
function workMyCollection(arr) {
return arr.reduce(function (promise, item) {
return promise.then(function (result) {
return doSomethingAsyncWithResult(item, result);
});
}, q());
}

Not quite as tidy, but certainly tidier than the original approach.

Ghost Promise

There’s a deterministic method (meaning it’s determined when the Promise starts executing, not based on the result during execution—Translator’s note) that sometimes requires asynchronous execution and sometimes doesn’t. So, to handle both cases, you create a Promise just to maintain consistency between asynchronous and synchronous scenarios (for abstraction and decoupling—Translator’s note), even though only one case might actually occur.

1
2
3
4
5
6
var promise;
if (asyncCallNeeded) promise = doSomethingAsync();
else promise = Q.resolve(42);
promise.then(function () {
doSomethingCool();
});

The above code isn’t the worst example of an anti-pattern, but it could be written more clearly—by wrapping value or promise with Q(). The Q() method accepts both a value and a promise as arguments:

1
2
3
4
5
6
7
Q(asyncCallNeeded ? doSomethingAsync() : 42)
.then(function (value) {
doSomethingGood();
})
.catch(function (err) {
handleTheError();
});

Note: Initially, I suggested using Q.when() for this scenario, but thanks to a comment from Kris Kowal, I was saved from this mistake. Don’t use Q.when(); just use Q(). The latter is much clearer.

Overeager Error Handler

The section title implies simultaneously setting then and rejected in fulfilled, hoping to use the rejected function to handle errors in fulfilled that also serve as parameters for the then function. However, this is impossible—the error in fulfilled can only be passed to the next then() and cannot be processed by the current rejected function. Hence, the section title is “Overeager”—it eagerly desires to handle errors, but those errors will never be passed to it for processing. —Translator’s Note

The then() method accepts two parameters: an operation function for the fulfilled state and an operation function for the rejected state. You may have written code like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
somethingAsync.then(
function () {
return somethingElseAsync();
},
function (err) {
handleMyError(err);
}
);
```

The problem with this approach is that `error` occurring in the `fulfilled` state will not be passed to the error-handling function.
The solution is to ensure the error-handling function is placed in a separate `then` method:

```js
somethingAsync
.then(function () {
return somethingElseAsync();
})
.then(null, function (err) {
handleMyError(err);
});
```

Or use `catch()`:

```js
somethingAsync
.then(function () {
return somethingElseAsync();
})
.catch(function (err) {
handleMyError(err);
});
```

This ensures any `error` occurring in the chain can be properly handled.

## The Forgotten Promise

You call a method that returns a `promise`, but you forget about this `promise` and then create another `promise`:

```js
var deferred = Q.defer();
doSomethingAsync().then(
function (res) {
res = manipulateMeInSomeWay(res);
deferred.resolve(res);
},
function (err) {
deferred.reject(err);
}
);

return deferred.promise;
```

This code completely abandons the elegance of `promsie`—there’s just too much unnecessary code.
The solution is simply to return the `promise` directly:

```js
return doSomethingAsync().then(function (res) {
return manipulateMeInSomeWay(res);
});
- EOF -
Originally published at: "Translation" Promise Anti-Patterns - Xheldon Blog