学习笔记:Promise(尚硅谷)

介绍

  • 本文主要记录在学习尚硅谷的 Promise 课程时的一些笔记
  • 尚硅谷前端学科全套课程请点击这里进行下载,提取码:afyt

一、基础知识

1.实例对象与函数对象

  • 实例对象:new 函数产生的对象,称为实例对象,简称为对象

  • 函数对象:将函数作为对象使用时,简称为函数对象

2.回调函数

  • 同步回调:

    • 理解:立即执行,完全执行完了才结束,不会放入回调队列中
    • 例子:数组遍历相关的函数、Promise 的 excutor 函数
  • 异步回调:

    • 理解:不会立即执行,会放入回调队列中将来执行
    • 例子:定时器调用、ajax 回调、Promise 的成功/失败回调

3.JS中的错误

  • 具体可看 MDN 中的解释

(1).错误的类型

  • Error:所有错误的父类型

  • ReferenceError:引用错误,引用的变量不存在

  • TypeError:类型错误,数据类型不正确

  • RangeError:范围错误,数据值不在其所允许的范围内

  • SyntaxError:语法错误

(2).错误处理

  • 捕获错误:使用 try...catch 来捕获

1
2
3
4
5
6
7
try {
let a;
console.log(a.xxx)
} catch (error){
console.log(error.message);
console.log(error.stack);
}
  • 抛出错误:使用 throw error 来抛出,error 中传一个 message

1
2
3
4
5
6
7
8
9
10
11
12
function something() {
if(Date.now() % 2 === 1){
console.log("当前时间为奇数,可以继续执行");
} else {
throw new Error("当前时间为偶数,无法继续执行");
}
}
try{
something();
} catch (error) {
alert(error.message);
}

(3).错误对象

  • message 属性:错误相关信息

  • stack 属性:函数调用栈记录信息

二、Promise

1.理解

  • 抽象表达:

    • Promise 是 JS 中进行异步编程的新方案
    • 注意:旧方案为纯回调
  • 具体表达:

    • 从语法上来说:Promise 是一个构造函数
    • 从功能上来说:Promise 对象用来封装一个异步操作并可以获取其结果

2.状态

Promise 一共有三种状态:pending、resolved、rejected

状态改变:

  • pending -> resolved,结果数据为 value

  • pending -> rejected,结果数据为 reason

一个 Promise 对象只能改变一次,无论变为成功还是失败,都会有一个结果数据

3.流程

  • 基本运行流程如下:
    promise01.png

4.使用

  • 基本使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建一个新的Promise对象
const p = new Promise((resolve, reject) => {// 执行器函数(同步回调)
// 执行异步操作任务
setTimeout(() => {
const time = Date.now();
if (time % 2 === 0){
// 成功,调用 resolve
resolve("当前时间为偶数"+time+"成功");
}else {
// 失败,调用 reject
reject("当前时间为奇数"+time+"失败");
}
}, 2000);
});
p.then(
value => {
console.log("成功的回调"+value);
},
reason => {
console.log("失败的回调"+reason);
}
)

5.优点

  • 指定回调函数的方式更加灵活:

    • 旧方法:必须在启动任务前指定
    • Promise:启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
  • 支持链式调用,可以解决回调地狱问题

!回调地狱

  • 回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件

  • 缺点:不便于阅读、不便于异常处理

  • 初级解决方案:promise 链式调用

1
2
3
4
5
6
7
8
9
10
11
doSomething()
.then(function (result){
return doSomethingElse(result)
})
.then(function (newResult){
return doThirdThing(newResult)
})
.then(function (finalResult){
console.log("Get the final result" + finalResult)
})
.catch(failureCallback)
  • 终极解决方案:async / await

1
2
3
4
5
6
7
8
9
10
async function request(){
try{
const result = await doSomething()
const newResult = await doSomethingElse(result)
const finalResult = await doThirdThing(newResult)
console.log("Get the final result" + finalResult)
}catch (error){
failureCallback(error)
}
}

6.API

  • Promise构造函数:Promise(excutor){}

    • excutor函数:同步执行 (resolve, reject) => {}
      • resolve函数:内部定义成功时我们调用的函数 value => {}
      • reject函数:内部定义失败时我们调用的函数 reason => {}
    • 说明:excutor 会在 Promise 内部立即同步回调,异步操作在执行器中执行
  • Promise.prototype.then方法:(onResolved, onRejected) => {}

    • onResolved函数:成功的回调函数 (value) => {}
    • onRejeced函数:失败的回调函数 (reason) => {}
    • 说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调,返回一个新的 promise 对象
  • Promise.prototype.catch方法:(onRejectde) => {}

    • onRejeced函数:失败的回调函数 (reason) => {}
    • 说明:then() 的语法糖,相当于 then(undefined, onRejected)
  • 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
2
3
4
5
reason => {
throw reason;
}
等同于:
reason => Promise.rejected(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 回调
  • 原理图如下:
    promise02.png

  • JS 执行时会区别这两个队列:

    • JS 引擎首先必须先执行所有的初始化同步任务代码
    • 每次准备取出第一个红任务执行前,都要将所有的微任务一个一个取出来执行

七、面试题

1.题一

  • 代码如下:

1
2
3
4
5
6
7
8
9
10
setTimeout(() => {
console.log(1)
},0)
Promise.resolve().then(() => {
console.log(2)
})
Promise.resolve().then(() => {
console.log(3)
})
console.log(4)
  • 输出结果为:4 2 3 1

2.题二

  • 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
setTimeout(() => {
console.log(1)
},0)
new Promise((resolve) => {
console.log(2)
resolve()
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
})
console.log(5)
  • 输出结果为:2 5 3 4 1

  • 研究:

    • 首先,Promise 的执行器函数是同步执行的,所以2先打印,之后同步执行5
    • 其次,放入微队列中的是3,宏队列中的是1,且在4处时 promise 状态还为 pending,所以其待定
    • 然后,微队列中的3打印输出,此时状态更改为,resolved,4进入微队列
    • 最后,微队列中的4打印输出,宏队列中的1打印输出

3.题三

  • 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const first = () => (
new Promise((resolve, reject) => {
console.log(3)
let p = new Promise((resolve, reject) => {
console.log(7)
setTimeout(() => {
console.log(5)
resolve(6)
},0)
resolve(1)
})
resolve(2)
p.then((arg) => {
console.log(arg)
})
})
)
first().then((arg) => {
console.log(arg)
})
console.log(4)
  • 输出结果为:3 7 4 1 2 5

4.题四

  • 代码如下:

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
setTimeout(() => {
console.log(0)
},0)
new Promise((resolve, reject) => {
console.log(1)
resolve()
}).then(() => {
console.log(2)
new Promise((resolve, reject) => {
console.log(3)
resolve()
}).then(() => {
console.log(4)
}).then(() => {
console.log(5)
})
}).then(() => {
console.log(6)
})
new Promise((resolve, reject) => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
  • 输出结果为:

  • 研究:

    • 首先根据代码执行顺序可以判断出当前同步执行的有[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]