异步编程的问题:
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) }
let URL = 'https://time.geekbang.com' xhr.open('Get', URL, true);
xhr.timeout = 3000 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) })
|
嵌套调用,下面的任务依赖上个任务的请求结果,并在上个任务的回调函数内部执行新的业务逻辑,这样嵌套层次多了以后,代码可读性变差
任务不确定性,执行每个任务有两种可能的结果(成功或者失败),所以体现在代码中就需要对每个任务的执行结果做两次判断,这种对每个任务都要进行一次额外的错误处理方式,明显增加了代码的混乱程度。
解决两个问题:
- 消灭嵌套调用
- 合并多个任务的错误处理
Promise如何消灭嵌套调用和多次错误处理
产生嵌套函数的主要原因就是在发起任务请求时会带上回调函数,这样当任务处理结束后,下个任务就只能在回调函数中处理
1.Promise实现回调函数延时绑定。在代码上体现就是先创建Promise对象x1,通过Promise的构造函数executor来执行业务逻辑,创建好Promise对象x1后,再使用x1.then设置回调函数:
1 2 3 4 5 6 7 8 9 10 11 12
| function executor(resolve, reject){ resolve(100) } let x1 = new Promise(executor)
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
| function executor(resolve, reject){ resolve(100) } let x1 = new Promise(executor)
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 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 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