浏览器工作原理——异步WebAPI(Promise)
JavaScript 引入 Promise 解决了异步编码风格的问题
以下是关于异步编码中遇到的具体问题与解决方式
- 代码逻辑不连续 –> 封装代码,让逻辑处理线性
- 回调地狱 –> 消灭嵌套调用与多次错误处理
- Promise 与 微任务
代码逻辑不连续
页面事件循环系统,页面中的任务都是在主线程上执行的,在执行如下载网络文件任务、获取摄像头等耗时任务,要放在页面主线程之外的进程或线程中去执行,避免耗时任务霸占页面主线程。
上图为一个异步编程编程模型,页面主线程发起一个耗时的任务,并将任务交给另一个进程处理,页面主线程继续执行消息队列中的任务。anotherProcess 处理完这个任务后,会将任务添加渲染进程的消息队列中,并排队等待循环系统处理。排队结束后,循环系统会取出消息队列中的任务进行处理,并触发相关回调操作。
如上是页面编程的一大特点 异步回调
web 页面的单线程架构决定了异步回调 异步回调影响到了我们的编码方式
使用 XMLHttpRequest 实现一个下载的需求
1 | // 失败回调 |
上面代码问题在于回调多,导致逻辑不连贯、不线性。接下来封装代码,降低处理异步回调次数
封装异步代码,让处理流程变得线性
关注 request(输入内容)和 response (输出内容),封装异步请求过程
- 把输入 http 请求的信息封装到一个 request 对象中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// makeRequest 用于构造 request 对象
function makeRequest(request_url) {
let request = {
method: 'Get',
url: request_url,
headers: '',
body: '',
credentials: false, //安全设置
sync: false,
responseType: 'text',
referrer: '',
timeout: 3000
}
return request
} - 封装请求过程,请求过程细节封装到 XFetch 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
*
* request 输入 http 的信息,如请求头、延时、返回类型等
* resolve 成功回调函数
* reject 失败回调函数
*/
function XFetch(request, resolve, reject) {
let xhr = new XMLHttpRequest()
xhr.ontimeout = function(e) {reject(e)}
xhr.onerror = function(e) {reject(e)}
xhr.onreadystatechange = function() {
if (xhr.state == 200) {
resolve(xhr.response)
}
}
// 创建一个请求
xhr.open(request.method, request.url, request.sync)
xhr.timeout = request.timeout
xhr.requestType = request.requestType
xhr.send()
} - 业务代码
1
2
3
4XFetch(
makeRequest('http://xxx.com'),
function resolve(data) {console.log(data)},
function reject(error){console.log(error)})新的问题:回调地域
多个接口请求相互依赖,就是存在请求之间嵌套,嵌套太多的回调函数,会使自己陷入 回调地狱,如下代码上面代码中, 嵌套 调用,内层的任务执行依赖外层,并在上个任务的回调函数内部执行新的业务逻辑,多层嵌套,代码可读性变差;另外执行每个任务都有成功或失败两种结果,体现在代码中就需要对每个任务结果做两次判断,这样每个任务都要进行一次错误处理,使得代码混乱。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23XFetch(
makeRequest('http://request1.com'),
function resolve(response) {
XFetch(
makeRequest('http://request2.com'),
function resolve(response) {
XFetch(
makeRequest('http://request3.com'),
function resolve(response) {
...
},
function reject(error) {
console.log(error)
}
)
},
function reject(error) {
console.log(error)
}
)
},
function reject(error) {console.log(error)}
)
Promise 解决的几个核心关键点 —— 消灭嵌套调用和多次错误处理
使用 Promise 重新构造 XFetch
1 | function XFetch(request){ |
如上:
- 使用 Promise,调用 XFetch 时,返回一个 Promise 对象
- 构建 Promise 对象时,传入 executor 函数,XFetch 主要业务在 executor 中执行
- 如果运行在 executor 函数中的业务执行成功了,会调用 resolve 函数;如果执行失败,调用 reject 函数
- 在 executor 函数中调用 resolve 函数时,会触发 promise.then 设置的回调函数;而调用 reject 函数时,会触发 promise.catch 设置的回调函数
使用 Promise 封装后的 XFetch,来解决嵌套调用和多次异常处理的问题
产生嵌套函数的主要原因是在发起任务请求时会带上回调函数,任务结束后,下个任务是在回调函数中处理的。
1 | var x1 = XFetch(makeRequest('http://request1.com')) |
Promise 解决嵌套回调的方式
- Promise 实现了回调函数的延时绑定。
- 需要将回调函数 onResolve 的返回值穿透到最外层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// executor 执行业务逻辑
function executor(resolve, reject) {
resolve()
}
// 创建 Promise 对象
var promiseObj1 = new Promise(executor)
// 回调函数
function onResolve(value) {
// 回调中可以执行下个异步请求,返回 promise 对象
return new Promise((resolve, reject) => {
resolve(value + 1)
})
}
//promiseObj1.then(onResolve) 延迟执行回调函数,不存在嵌套
var promiseObj2 = promiseObj1.then(onResolve)
// 取出上个回调返回的 promise
promiseObj2.then(res => {
console.log(res)
})
Promise 通过 回调函数延迟绑定 和 回调函数返回值穿透的技术 解决循环嵌套
的问题
promise 是怎么处理异常的
1 | // 业务处理代码 |
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被 onReject 函数处理或 catch 语句捕获为止
Promise 与 微任务
1 | function executor(resolve, reject) { |
代码执行顺序:
- 首先执行 new Promise 时,Promise 的构造函数会被执行
- Promise 构造函数会调用参数 executor 函数。executor 中执行 resolve, resolve 内部调用 demo.then 设置的函数 onResolve
browmise
1 | function Bromise() { |
onResolve_ is not a function 加入定时器让 onResolve 延迟执行
上面采用了定时器来推迟 onResolve 的执行,不过使用定时器的效率并不是太高,好在我们有微任务,所以 Promise 又把这个定时器改造成了微任务了,这样既可以让 onResolve_ 延时被调用,又提升了代码的执行效率