学习笔记: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]