0%

使用Promise解决代码嵌套问题

问题

现在有多个请求,第一个请求结束后,失败就结束,成功就继续执行第二个请求,第二个请求结束后,失败就结束,成功就继续执行第三个请求,……,这样一直下去,可能会有三个请求,或者五个请求,或者更多。

原有的解决方法

按照以往的思路,会写出如下的嵌套代码:

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
a(function(res) {
if (res) {
console.log('a执行成功');
b(function(res) {
if (res) {
console.log('b执行成功');
c(function(res) {
if (res) {
console.log('c执行成功');
} else {
console.log('c执行失败,结束');
}
});
} else {
console.log('b执行失败,结束');
}
});
} else {
console.log('a执行失败,结束');
}
});

function a(callback) {
callback(true);
}

function b(callback) {
callback(true);
}

function c(callback) {
callback(true);
}

使用Promise解决嵌套回调

如果请求数再多一点,那嵌套会更加严重,可读性很差,这时可以用Promise进行优化,如下:

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
a().then(value => {
console.log(value);
return b();
}, reason => {
console.log(reason);
return b();
}).then(value => {
console.log(value);
return c();
}, reason => {
console.log(reason);
return c();
}).then(value => {
console.log(value);
}, reason => {
console.log(reason);
});

function a() {
return new Promise((resolve, reject) => {
if (true) {
resolve('a执行成功');
} else {
reject('a执行失败,结束');
}
})
}

function b() {
return new Promise((resolve, reject) => {
if (true) {
resolve('b执行成功');
} else {
reject('b执行失败,结束');
}
})
}

function c() {
return new Promise((resolve, reject) => {
if (true) {
resolve('c执行成功');
} else {
reject('c执行失败,结束');
}
})
}

每个函数都不再采用callback进行回调,而是改为返回Promise对象,外部就可以在then里面获取到执行结果,然后继续调用下一个函数,还是返回Promise对象,在下一个then里获取到。

这里的关键就是:then里面返回的结果,会在下一个then里拿到,这里所说的结果,包括某个值,或者是(resolve, reject)这样的回调。

如何提前终止Promise链?

上面的代码还是有问题的,如果在中间某个环节出现了reject,它只会导致下一个then执行到reject分支,下下个then又是执行了resolve分支,很明显,我们期望是只要有一个reject了,后续都要执行reject分支。

这个问题的原因是因为在于我们处理了reject分支,但reject里又没有返回新的Promise,导致下个then进入resolve分支,这里解决的方法有两个:

  1. 改成都返回一个reject状态的Promise对象。
  2. 直接把前面的reject分支都去掉,最后面的then才处理reject。

如何实现延时有序执行?

如果b()需要延时5s后执行,要怎么写?显然不能写成这样:

1
2
3
setTimeout(function(){
return b();
}, 5000);

setTimeout里面的任务会放到宏任务队列,而Promise的任务会被放到微任务队列,两者之间没有执行顺序约束,而我们是要在b执行后才能执行c,所以并不能实现我们的目的,这里的解决方法,就是要保证setTimeout里面的任务执行结束,才能继续执行接下来的Promise任务,可以引入一个中间Promise实现,定义delay函数:

1
2
3
4
5
6
7
function delay(delayTime) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve();
}, delayTime);
});
}

这里在调用delay函数后,当超时时间到了,setTimeout里的任务执行了,才会回调resolve,然后才继续执行接下来的then里面的代码。

最终Promise代码

解决了上面两个常见问题,最终的代码如下:

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
64
65
66
67
68
69
70
71
72
73
74
hello().then(value => {
console.log('成功');
}, reason => {
console.log('失败');
});

function hello() {
return new Promise((resolve, reject) => {
console.log('执行a');
a().then(
value => {
return delay(2000);
}, reason => Promise.reject(reason)).then(value => {
console.log('执行b');
return b();
}, reason => Promise.reject(reason)).then(value => {
console.log('执行c');
return c();
}, reason => Promise.reject(reason)).then(value => {
console.log('执行d');
return d();
}, reason => Promise.reject(reason)).then(
resolve, reject
);
});
}

function a() {
return new Promise((resolve, reject) => {
if (true) {
resolve('a执行成功');
} else {
reject('a执行失败,结束');
}
})
}

function b() {
return new Promise((resolve, reject) => {
if (false) {
resolve('b执行成功');
} else {
reject('b执行失败,结束');
}
})
}

function c() {
return new Promise((resolve, reject) => {
if (true) {
resolve('c执行成功');
} else {
reject('c执行失败,结束');
}
})
}

function d() {
return new Promise((resolve, reject) => {
if (true) {
resolve('d执行成功');
} else {
reject('d执行失败,结束');
}
})
}

function delay(delayTime) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve();
}, delayTime);
});
}