0%

Promise告别回调函数

异步编程的问题:

1.代码逻辑不连续

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 执行状态
function onResolve(response){console.log(response) }
function onReject(error){console.log(error) }

let xhr = new XMLHttpRequest()
xhr.ontimeout = function(e) { onReject(e)}
xhr.onerror = function(e) { onReject(e) }
xhr.onreadystatechange = function () { onResolve(xhr.response) }

// 设置请求类型,请求 URL,是否同步信息
let URL = 'https://time.geekbang.com'
xhr.open('Get', URL, true);

// 设置参数
xhr.timeout = 3000 // 设置 xhr 请求的超时时间
xhr.responseType = "text" // 设置响应返回的数据格式
xhr.setRequestHeader("X_TEST","time.geekbang")

// 发出请求
xhr.send();

上述代码包含五个回调,导致代码逻辑不连贯,不线性,这就是异步回调影响我们的编程方式。

2.回调地狱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
XFetch(makeRequest('https://time.geekbang.org/?category'),
function resolve(response) {
console.log(response)
XFetch(makeRequest('https://time.geekbang.org/column'),
function resolve(response) {
console.log(response)
XFetch(makeRequest('https://time.geekbang.org')
function resolve(response) {
console.log(response)
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
  1. 嵌套调用,下面的任务依赖上个任务的请求结果,并在上个任务的回调函数内部执行新的业务逻辑,这样嵌套层次多了以后,代码可读性变差

  2. 任务不确定性,执行每个任务有两种可能的结果(成功或者失败),所以体现在代码中就需要对每个任务的执行结果做两次判断,这种对每个任务都要进行一次额外的错误处理方式,明显增加了代码的混乱程度。

解决两个问题:

  1. 消灭嵌套调用
  2. 合并多个任务的错误处理

Promise如何消灭嵌套调用和多次错误处理

产生嵌套函数的主要原因就是在发起任务请求时会带上回调函数,这样当任务处理结束后,下个任务就只能在回调函数中处理

1.Promise实现回调函数延时绑定。在代码上体现就是先创建Promise对象x1,通过Promise的构造函数executor来执行业务逻辑,创建好Promise对象x1后,再使用x1.then设置回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
// 创建 Promise 对象 x1,并在 executor 函数中执行业务逻辑
function executor(resolve, reject){
resolve(100)
}
let x1 = new Promise(executor)


//x1 延迟绑定回调函数 onResolve
function onResolve(value){
console.log(value)
}
x1.then(onResolve)

2.将回调函数onResolve的返回值穿透到最外层,因为我们会根据onResolve函数的传入值来决定创建什么类型的Promise任务,创建好的Promise对象需要返回到最外层,这样就可以摆脱嵌套循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建 Promise 对象 x1,并在 executor 函数中执行业务逻辑
function executor(resolve, reject){
resolve(100)
}
let x1 = new Promise(executor)


//x1 延迟绑定回调函数 onResolve
function onResolve(value){
console.log(value)
let x2 = new Promise((resolve,reject) =>{
resolve(value+1)
})
return x2;//内部返回值穿透到最外层
}
x1.then(onResolve)

处理异常:

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
function executor(resolve, reject) {
let rand = Math.random();
console.log(1)
console.log(rand)
if (rand > 0.5)
resolve()
else
reject()
}
var p0 = new Promise(executor);

var p1 = p0.then((value) => {
console.log("succeed-1")
return new Promise(executor)
})

var p3 = p1.then((value) => {
console.log("succeed-2")
return new Promise(executor)
})

var p4 = p3.then((value) => {
console.log("succeed-3")
return new Promise(executor)
})

p4.catch((error) => {
console.log("error")
})
console.log(2)

这段代码四个Promise对象,无论哪个对象抛出异常,都可以通过最后一个对象p4.catch捕获异常,通过这种方式可以将所有Promise对象的错误合并到一个函数来处理,这样就解决了每个任务需要单独处理异常的问题。Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被onReject函数处理或catch语句捕获为止。具备这样的“冒泡”特性后,就不需要在每个Promise对象中单独捕获异常。

Promise与微任务

由于Promise采用回调函数延迟绑定技术,所以在执行resolve函数时,回调函数还没有绑定,那么只能推迟回调函数的执行

1
2
3
4
5
6
7
8
9
10
11
12
function Promise(executor){
var onResolve_=null
var onReject_=null
//模拟实现resolve和then
this.then=function(onResolve,onReject){
onResolve_ = onResolve
}
function resolve(value){
onResolve_(value)
}
executor(resolve,null)
}

执行这段代码:

1
2
3
4
5
6
7
8
function executor(resolve,reject){
resolve(100)
}
let demo = new Promise(executor)
function onResolve(value){
console.log(value)
}
demo.then(onResolve)

代码报错是由于Promise的延迟绑定导致的,在调用onResolve_时,Promise.then还没执行,所以会报onResolve_ is not a function错误

因此,改造Promise的resolve方法,让resolve延迟调用onResolve_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Promise(executor){
var onResolve_=null
var onReject_=null
//模拟实现resolve和then
this.then=function(onResolve,onReject){
onResolve_ = onResolve
}
function resolve(value){
setTimeout(()=>{
onResolve_(value)
},0)
}
executor(resolve,null)
}

用定时器推迟onResolve执行,用定时器效率低,因此用微任务

参考链接:

https://blog.poetries.top/browser-working-principle/guide/part4/lesson19.html#promise-%E6%B6%88%E7%81%AD%E5%B5%8C%E5%A5%97%E8%B0%83%E7%94%A8%E5%92%8C%E5%A4%9A%E6%AC%A1%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86