Promise构造函数的反模式

先从一个例子来看看Promise的常见的使用错误。

Bad Code

1
2
3
4
5
6
7
// Bad example! Spot 3 mistakes!

doSomething().then(function(result) {
doSomethingElse(result) // Forgot to return promise from inner chain + unnecessary nesting
.then(newResult => doThirdThing(newResult));
}).then(() => doFourthThing());
// Forgot to terminate chain with a catch!

错误解析

  • The first mistake is to not chain things together properly. This happens when we create a new promise but forget to return it. As a consequence, the chain is broken, or rather, we have two independent chains racing. This means doFourthThing() won’t wait for doSomethingElse() or doThirdThing() to finish, and will run in parallel with them, likely unintended. Separate chains also have separate error handling, leading to uncaught errors.(这样会导致多个独立的Promise链并行运行,而且不同的链需要不同的错误处理,这样会导致一些我们没有预期的错误。)

  • The second mistake is to nest unnecessarily, enabling the first mistake. Nesting also limits the scope of inner error handlers, which—if unintended—can lead to uncaught errors. A variant of this is the promise constructor anti-pattern, which combines nesting with redundant use of the promise constructor to wrap code that already uses promises.(第二个错误是不必要的嵌套,会诱发第一个问题,而且也有错误处理问题,一个变体就是promise 构造函数反模式,这个我会在下面给出示例)

  • The third mistake is forgetting to terminate chains with catch. Unterminated promise chains lead to uncaught promise rejections in most browsers.(第三个错误就是没有用catch来终止promise链)

Good Code

1
2
3
4
5
6
7
doSomething()
.then(function(result) {
return doSomethingElse(result);
})
.then(newResult => doThirdThing(newResult))
.then(() => doFourthThing())
.catch(error => console.error(error));

反模式定义

下面来说说Promise的构造函数反模式,这是它的定义which combines nesting with redundant use of the promise constructor to wrap code that already uses promises.

Example1:

1
2
3
4
5
6
7
8
9
10
function getStuffDone(param) {
return new Promise(function(resolve, reject) {
// using a promise constructor
myPromiseFn(param)
.then(function(val) {
resolve(val);
}).catch(function(err) {
reject(err);
});
}

Iview admin中对反模式的使用

另外再看一下iview admin中的应用(我只想说这个代码写的好迷呀🙄),违反了上面的注意事项,有两条不同的链,而且如果其中一个函数出错,那么肯定是没有得到处理,我觉得如果使用的化,最起码在两处加上catch语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 this.handleLogin({ userName, password }).then(res => {
this.getUserInfo().then(res => {
this.$router.push({
name: this.$config.homeName
})
})
})
//改成链式
this.handleLogin({ userName, password }).then(res => {
return this.getUserInfo()
}).then(res => {
this.$router.push({
name: this.$config.homeName
})
})
}).catch(error => throw new Error('login error'))

Vuex中的代码(代码里加了注释)

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
handleLogin ({ commit }, { userName, password }) {
userName = userName.trim()
//这里用了Promise的构造函数反模式
return new Promise((resolve, reject) => {
login({
userName,
password
}).then(res => {
const data = res.data
commit('setToken', data.token)
resolve()
}).catch(err => {
reject(err)
})
})
}

getUserInfo ({ state, commit }) {
//这里用了Promise的构造函数反模式,这里注意最后TA使用了Try Catch来解决不同链的错误处理
return new Promise((resolve, reject) => {
try {
getUserInfo(state.token).then(res => {
const data = res.data
....
commit('setAccess', data.access)
resolve(data)
}).catch(err => {
reject(err)
})
} catch (error) {
reject(error)
}
})
},

总结

Quoting Esailija:

This is the most common anti-pattern. It is easy to fall into this when you don’t really understand promises and think of them as glorified event emitters or callback utility. Let’s recap: promises are about making asynchronous code retain most of the lost properties of synchronous code such as flat indentation and one exception channel.

参考文章:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises