写在前面:
第一遍学Promise时, 只是大概过了一遍, 感觉学的不够深入, 这一篇算是对之前的一个总结吧. Promise在ES6中也属于一个较难理解的一部分; 所以在学习一个比较难理解的知识点时, 我们可以围绕这个知识点进行展开,逐个去理解.
再探Promise
理解一个知识点, 不妨先列出下面几个问题.
- Promise是用来干什么的?
- Promise是什么?
- Promise如何去创建,使用?
- Promise的常用形式?
- Promise的使用有哪些注意点?
异步相关背景介绍
浏览器内核
首先聊一下浏览器, 一直对浏览器的结构比较好奇,查了很多资料总结就有下面一点相关总结; 其中也借鉴其他人的一些东西.
浏览器是多进程的,有主进程, GPU加速进程,渲染进程(内核)等, 一般新开一个tab页面就是新启动一个进程, CPU就会给他分配资源; 但其中有一个核心进程==>渲染进程(浏览器内核), 是我们前端人员需要特别关注的,它包括了多个线程...
GUI渲染线程
负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了), GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
JS引擎线程
也称为JS内核,负责处理Javascript脚本程序.(例如V8引擎)JS引擎线程负责解析Javascript脚本,运行代码。 JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。 不过H5中新增了Web Worker, 实现了多线程: js会新开线程来处理一些其他任务,但不会影响DOM结构... 创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM) JS引擎线程与worker线程间通过特定的方式通信 (postMessage API,需要通过序列化对象来与线程交互特定的数据)
事件触发线程
这个线程是归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助) 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求, 页面滚动等),会将对应任务添加到事件线程中 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理 注意, 由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
定时触发器线程
即setInterval与setTimeout所在线程 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确); 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行) 当然setTimeout中的延时参数也不一定准确
异步HTTP请求线程
在XMLHttpRequest在连接后是通过浏览器新开一个网络线程去请求 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件, 将这个回调再放入事件队列中。再由JavaScript引擎执行。
那么关于浏览器方面的背景知识就介绍到这里啦, 想要深入去了解,可以去查相关资料...
事件队列和循环
大家都知道JavaScript引擎是单线程的工作模式, 即同一时间只能跑一段代码,还要按顺序自上而下执行; 但是碰到I/O操作, 定时器, 事件监听函数等这些耗时操作; JS引擎不会等待它们有结果了才去之下它们后面的代码, 而是会将它们扔进任务(事件)队列中, 等待同步代码执行栈空了之后, 再去任务队列将任务一个个取出来执行任务所对应的回调函数, 执行完毕后会一直等待新的任务到来; 如此循环...
几个类型的回调
- 同步回调函数
我们可以利用了函数的执行栈顺序,函数作为参数放到另一个函数中调用, 谁在后面调用谁就先被放在函数执行栈栈顶
- 异步回调函数
事先在外面定义好一个callback; 将回调函数作为某个函数的参数, 利用函数的作用域将函数中异步任务得到的结果存在回调函数的形参中, 然后在函数体末尾调用...
- 定时器
setTimeout的作用是在间隔一定的时间后,将回调函数插入任务队列中,等栈中的同步任务都执行完毕后,再执行, 当然这个时间不一定准确...
Promise是用来干什么的?
看阮老师的ES6出门上说Promise是JS异步编程的一种解决方案. 举个例子, Ajax的回调问题, 如果下一个ajax请求要用到上一个Ajax请求中的结果, 那么往往就会导致多个回调嵌套的问题, 那么Promise就可以解决这种代码上的嵌套问题, 是我们的代码变得更优美, 更利于维护; 我暂时先对Promise的理解就是: 处理异步任务, 保存异步结果状态, 异步代码同步化...
Promise是什么?
Promise 它就是一个对象,相当于一个容器, 里面存的就是一个异步操作的结果; 我们可以是从中获取异步操作结果的相关信息。
Promise对象代表一个未完成、但预计将来会完成的操作。
它有以下三种状态:
pending:初始值,不是fulfilled,也不是rejected
fulfilled:代表操作成功
rejected:代表操作失败
Promise有两种状态改变的方式,既可以从pending转变为fulfilled,也可以从pending转变为rejected。一旦状态改变,就「凝固」了,会一直保持这个状态,不会再发生变化。当状态发生变化,promise.then绑定的函数就会被调用。
注意:Promise一旦新建就会「立即执行」(它属于microtask),无法取消。这也是它的缺点之一。
Promise的创建和使用?
1.创建promise对象
//1.使用new Promise(func)的形式
//2.快捷语法: Promise.resolve(func) || Promise.reject(func)
// 参数1: 一般是一个处理异步任务的函数
// 返回值: 一个promise实例对象
Promise.resolve('foo')
// 等价于, 不过参数类型不一样执行的操作也会有所不同
new Promise(resolve => resolve('foo'))
2.在函数func中 放异步处理代码
// 传入两个参数: 回调函数resolve, reject分别去保存异步处理的结果
// 成功: 使用resolve(结果)
// 失败: 使用reject(原因)
3.调用实例的then(func1) 或者 catch(err)
首先then方法是异步执行, 对上面的异步结果进行处理的函数
参数: 传回调函数, 一个两个都行, 前者是成功状态的回调,后者是失败的回调
Promise常用的场景?
promise一般的使用套路就是:
1.先定义一个函数, 函数内部使用new Promise()的方式来返回一个promise对象, resolve用来保存 异步处理成功的结果 reject用来保存 异常处理的结果 2.然后函数调用,传参 3.链式语法点出then方法, then中的回调用来处理异步结果 4.有错误就点出catch方法, 也可以用then(null, function() {})代替catch 5.then的回调中也可return一个值, 会被包装成一个新的promise, 因此可以继续调用then方法
应用场景: 在ajax中使用, 解决异步嵌套问题
function ajax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
// 请求类型, 地址, 异步
xhr.open('get', url, true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
try {
// 处理响应内容, 将内容丢到成功状态的回调
resolve(JSON.parse(xhr.responseText))
} catch (e) {
// 捕获错误, 丢到失败状态的回调
reject(e)
}
}
}
});
}
// 调用 封装的ajax函数
let url = 'http://127.0.0.1:3000/xxoo'; // 自己本地开的一个服务
ajax(url)
.then(res => console.log(res)) // 输出 {code: 0, msg: 'hello cors'}
.catch(err => console.log(err))
```
其他场景
// 实现串行任务管道; 即当前任务的输出可以作为下一个任务的输入,形成一条数据管道; // 比如: 比如从url1获取参数userId,拿到后再从url2获取第三方openId,最后再从url3货取orderList,然后把结果展示给用户,类似的逻辑都是任务管道: new Promise(function(resolve, reject) { resolve(1); }) .then(function(res) { return new Promise(function(resolve, reject) { resolve(res + 1); }); }) .then(function(res) { return new Promise(function(resolve, reject) { resolve(res + 1); }); }) .then(function(res) { console.log(res); // 3 });
promise的好处
- 在异步执行的流程中,使用Promise可以把 执行代码 和 处理结果 的代码清晰地分离
这样我们便可以 把执行代码 和 结果处理 分成不同的模块来写,易于维护 - 减少异步回调的嵌套, 比如ajax回调, 我们可以依次调用then方法即可, 还可以控制回调的顺序
- 多个异步任务是为了容错去访问用同一资源时, 可以使用Promise.race([promise实例...])
- 多个异步任务并行执行时,比如ajax访问两个接口, 可以用Promise.all([promise实例...])
promose使用的注意事项
- Promise构造函数内的同步代码立即执行
- 回调函数参数resolve异步执行, 将结果作为参数传给then方法中的回调函数
- resolve只有第一次执行有效,状态不能二次改变
- then和catch如果有return, 返回的是一个全新的promise对象, 可以链式调用
- Promise构造函数只会执行一次, promise实例会保存resolve的状态,
以后这个实例每次调用then都是返回一个这个状态, 若链式调用then,下一个则会打印undefined, res没有值... - then中返回任意一个非 promise 的值都会被包裹成 promise 对象
- .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环
- .then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。
- .then 可以接收两个参数,第一个是处理成功的函数,第二个是处理错误的函数。.catch 是 .then 第二个参数的简便写法,但是它们用法上有一点需要注意:.then 的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch 可以捕获之前的错误。