学习笔记:Promise(尚硅谷)
介绍
- 本文主要记录在学习尚硅谷的 Promise 课程时的一些笔记
- 尚硅谷前端学科全套课程请点击这里进行下载,提取码:afyt
一、基础知识
1.实例对象与函数对象
实例对象:new 函数产生的对象,称为实例对象,简称为对象
函数对象:将函数作为对象使用时,简称为函数对象
2.回调函数
同步回调:
- 理解:立即执行,完全执行完了才结束,不会放入回调队列中
- 例子:数组遍历相关的函数、Promise 的 excutor 函数
异步回调:
- 理解:不会立即执行,会放入回调队列中将来执行
- 例子:定时器调用、ajax 回调、Promise 的成功/失败回调
3.JS中的错误
具体可看 MDN 中的解释
(1).错误的类型
Error
:所有错误的父类型ReferenceError
:引用错误,引用的变量不存在TypeError
:类型错误,数据类型不正确RangeError
:范围错误,数据值不在其所允许的范围内SyntaxError
:语法错误
(2).错误处理
捕获错误:使用
try...catch
来捕获
1 | try { |
抛出错误:使用
throw error
来抛出,error 中传一个 message
1 | function something() { |
(3).错误对象
message 属性:错误相关信息
stack 属性:函数调用栈记录信息
二、Promise
1.理解
抽象表达:
- Promise 是 JS 中进行异步编程的新方案
- 注意:旧方案为纯回调
具体表达:
- 从语法上来说:Promise 是一个构造函数
- 从功能上来说:Promise 对象用来封装一个异步操作并可以获取其结果
2.状态
Promise 一共有三种状态:pending、resolved、rejected
状态改变:
pending -> resolved,结果数据为 value
pending -> rejected,结果数据为 reason
一个 Promise 对象只能改变一次,无论变为成功还是失败,都会有一个结果数据
3.流程
基本运行流程如下:
4.使用
基本使用方法如下:
1 | // 创建一个新的Promise对象 |
5.优点
指定回调函数的方式更加灵活:
- 旧方法:必须在启动任务前指定
- Promise:启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
支持链式调用,可以解决回调地狱问题
!回调地狱
回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件
缺点:不便于阅读、不便于异常处理
初级解决方案:promise 链式调用
1 | doSomething() |
终极解决方案:async / await
1 | async function request(){ |
6.API
Promise构造函数:
Promise(excutor){}
- excutor函数:同步执行
(resolve, reject) => {}
- resolve函数:内部定义成功时我们调用的函数
value => {}
- reject函数:内部定义失败时我们调用的函数
reason => {}
- resolve函数:内部定义成功时我们调用的函数
- 说明:excutor 会在 Promise 内部立即同步回调,异步操作在执行器中执行
- excutor函数:同步执行
Promise.prototype.then方法:
(onResolved, onRejected) => {}
- onResolved函数:成功的回调函数
(value) => {}
- onRejeced函数:失败的回调函数
(reason) => {}
- 说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调,返回一个新的 promise 对象
- onResolved函数:成功的回调函数
Promise.prototype.catch方法:
(onRejectde) => {}
- onRejeced函数:失败的回调函数
(reason) => {}
- 说明:
then()
的语法糖,相当于then(undefined, onRejected)
- onRejeced函数:失败的回调函数
Promise.resolve方法:
(value) => {}
- value:成功的数据或promise对象
- 说明:返回一个成功/失败的promise对象
Promise.reject方法:
(reason) => {}
- reason:失败的原因
- 说明:返回一个失败的promise对象
Promise.all方法:
(Promises) => {}
- promises:包含n个promise数组
- 说明:返回一个新的promise,只有所有的promise都成功才成功,只要有一个失败就直接失败
Promise.race方法:
(promises) => {}
- promises:包含n个promise数组
- 说明:返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态
三、Promise问题
1.如何改变Promise状态?
调用
resolve(value)
:如果当前是pending就会变成resolved调用
reject(reason)
:如果当前是pending就会变成rejected抛出异常:如果当前是pending就会变成rejected
2.一个Promise指定多个成功/失败回调函数,都会调用吗?
当 promise 改变为对应状态时都会调用
3.改变Promise状态和指定回调函数谁先谁后?
都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态在指定回调
(1).如何先改状态再指定回调?
在执行器中直接调用
resolve()/reject()
延迟更长时间再调用
then()
(2).什么时候才能得到数据?
如果先指定回调,那当状态发生改变时,回调函数就会调用,得到数据
如果先改变状态,那当指定回调时,回调函数就会调用,得到数据
4.then()方法返回的新promise的结果状态由什么决定?
简单表达:由
then()
指定的回调函数执行的结果决定详细表达:
- 如果抛出异常,新 promise 变为 rejected,reason 为抛出的异常
- 如果返回的是非 promise 的任意值,新 promise 变为 resolved,value 为返回的值
- 如果返回的是另一个新 promise,此 promise 的结果就会变成新 promise 的结果
5.promise如何串联多个操作任务?
promise 的
then()
返回一个新的 promise,可以看成then()
的链式调用通过
then()
的链式调用串联多个同步/异步任务
6.promise异常传/穿透是什么?
当使用 promise 的
then()
链式调用时,可以在最后指定失败的回调,前面任何操作出了异常,都会传到最后失败的回调中处理因为在中间的传递中,默认执行如下语句:
1 | reason => { |
7.如何中断promise链?
定义:当使用 promise 的
then()
链式调用时,在中间中断,不再调用后面的回调函数方法:在回调函数中返回一个 pending 状态的 promise 对象
1 | return new Promise(() => {}); |
四、自定义Promise
重复观看!!!
五、async与await
1.async函数
函数的返回值为 promise 对象
promise 对象的结构由 async 函数执行的返回值决定
2.await表达式
await 右侧的表达式一般为 promise 对象,但也可以是其他的值
- 如果表达式是 promise 对象,await 返回的是 promise 成功的值
- 如果表达式时其他值,直接将此值作为 await 的返回值
- 如果 await 的 promise 失败了,就会抛出异常,需要通过
try...catch
来捕获处理
注意:await 必须写在 async 函数中,但 async 函数中可以没有 await
六、JS异步之两种队列
JS 中用来存储待执行回调函数的队列包含两个不同特定的队列:
- 宏队列:用来保存待执行的宏任务(回调)。如:定时器回调、DOM 事件回调、ajax 回调
- 微队列:用来保存待执行的微任务(回调)。如:Promise 回调、MutationObserve 回调
原理图如下:
JS 执行时会区别这两个队列:
- JS 引擎首先必须先执行所有的初始化同步任务代码
- 每次准备取出第一个红任务执行前,都要将所有的微任务一个一个取出来执行
七、面试题
1.题一
代码如下:
1 | setTimeout(() => { |
输出结果为:4 2 3 1
2.题二
代码如下:
1 | setTimeout(() => { |
输出结果为:2 5 3 4 1
研究:
- 首先,Promise 的执行器函数是同步执行的,所以2先打印,之后同步执行5
- 其次,放入微队列中的是3,宏队列中的是1,且在4处时 promise 状态还为 pending,所以其待定
- 然后,微队列中的3打印输出,此时状态更改为,resolved,4进入微队列
- 最后,微队列中的4打印输出,宏队列中的1打印输出
3.题三
代码如下:
1 | const first = () => ( |
输出结果为:3 7 4 1 2 5
4.题四
代码如下:
1 | setTimeout(() => { |
输出结果为:
研究:
- 首先根据代码执行顺序可以判断出当前同步执行的有[1 7],宏队列有[0],微队列有[2 8]
- 然后输出[1 7 2],当2执行过后3为同步,所以输出为[1 7 2 3],此时宏队列有[0],微队列中有[8 4 6],因为2所在的代码块中返回值为 undefined,所以6已经入队列了
- 然后输出[1 7 2 3 8 4],当4执行完后5为promise对象的回调,此时宏队列有[0],微队列中有[6 5]
- 然后输出[1 7 2 3 8 4 6],此时宏队列有[0],微队列中有[5 8]
- 最后输出[1 7 2 3 8 4 6 5 0]