Table of Contents

image-20240218095113458

Promise

是什么?

promise是ES6引入的异步编程新的解决方案, 从语法上来说它是一个构造函数,可以实例化对象,封装异步操作,获取成功或失败的结果

了解js异步执行, JS是单线程的, 浏览器不是单线程的, js调用的某些api也不是单线程的

所以只是执行代码的线程是单线程

event loop 是用于检测 call stack 和 queue, 一旦call stack 所有任务都结束了(或者说 只要queue发生变化), event loop 就会从queue中取出第一个回调开始执行

image-20230725172504590

注: 倒计时器只要开始之后就会自动开始倒计时, 不管call stack 和 queue中是什么情况

image-20230725172152640

其优点是:支持链式调用,可以解决回调地狱问题、指定回调的方式更为灵活

  1. 抽象表达:

    Promise 是一门新的技术(ES6 规范)

    Promise 是 JS 中进行异步编程的新解决方案

    备注:旧方案是单纯使用回调函数

  2. 具体表达:

    从语法上来说: Promise 是一个构造函数

    从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/ 失败的结果值

异步编程

  • fs 文件操作

    require('fs').readFile('./index.html', (err,data)=>{})
    
  • 数据库操作

  • AJAX

      $.get('/server', (data)=>{})
    
  • 定时器

    setTimeout(()=>{}, 2000);
    

为什么

1.指定回调函数的方式更加灵活

  • 旧的: 必须在启动异步任务前指定

  • promise: 启动异步任务=> 返回promie对象=> 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)

  1. 支持链式调用,可以解决回调地狱问题

    什么是回调地狱?

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

    回调地狱的缺点? 不便于阅读, 不便于异常处理

基本使用

需求

点击按钮, 1s 后显示是否中奖(30%概率中奖)

​ 若中奖弹出 恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券

​ 若未中奖弹出 再接再厉

实现:

  • html

    <div class="container">
            <h2 class="page-header">Promise 初体验</h2>
            <button class="btn btn-primary" id="btn">点击抽奖</button>
        </div>
    
  • script

    image-20230722170353662

    image-20230722170407313

  • 实践练习 fs

image-20230722172619940

  • 实践练习2 AJAX

    image-20230722173612206

  • 封装一个函数

    image-20230722174128313

promisify

image-20230722174233205

image-20230722175621474

Promise封装AJAX请求

image-20230722180423869

Promise 状态的改变

实际上就是promise实例对象中的一个属性, 叫做 PromiseState

image-20230722180646375

  1. pending 变为 resolved / fulfilled

  2. pending 变为 rejected

说明: 只有这 2 种, 且一个 promise 对象只能改变一次 无论变为成功还是失败, 都会有一个结果数据

成功的结果数据一般称为 value, 失败的结果数据一般称为 reason

Promise 对象的值

实例对象中的另一个属性 『PromiseResult』
保存着异步任务『成功/失败』的结果

  • resolve
  • reject

Promise 的基本流程

image-20230722182553715

Promise API

有三个, 分别是

  • new Promise(executor)

    image-20230722183502438

  • then方法

    image-20230722183520537

  • catch

    image-20230722183135799

一般来说回调函数是执行完所有代码后执行的(异步),这里111在222前面输出证明是同步

image-20230722182930376

promise内部的操作是同步代码,then方法是异步代码

catch等于是把then的后一个函数拆出来,单独做成语法糖

image-20230722183257150

Promise 的方法

  • Promise.resolve(); 属于Promise函数对象, 并不属于实例对象

image-20230723115937180

  • promise.reject();

    跟上面一样, 只不过都是拒绝

  • promise.all();
    image-20230723123134573

image-20230723122205022

image-20230723122145895

  • promise.race();

    类似赛跑, 谁先改变状态, 谁就决定promise 的结果

image-20230723123125826

image-20230723123045578

image-20230723123040527

promise 的几个关键问题

  1. 如何修改promise的状态

    image-20230724123102052

    throw 'error!';
    
  2. 如果使用then方法为一个promise对象指定多个回调, 那么这些回调会不会都执行?

    会, 当 promise 改变为对应状态时都会调用

    回调1 和 2 都会执行, 但如果我上面没有调用resolve, 那下面的回调就不会执行

    image-20230724123427312

  3. 改变 promise 状态 和 指定回调函数(then或者catch) 谁先谁后?

    看同步还是异步, 改变状态里面是一个异步任务的时候, 那就是then先被指定, 后改变状态, 然后再执行回调

    image-20230724124319089

    拿到数据指的是: 指定的回调函数什么时候执行

    要注意: 这里指的是then回调函数的执行, 不是里面的成功和失败的回调结果

    所以:

    如果我先改变了状态(直接调用resolve), then方法在调用的时候就会执行里面的回调去处理成功或失败的结果

    如果我先执行回调(then), 后续改变状态, 那么改变状态以后再去调用成功或失败的结果

    指定回调---改变状态---执行回调

    是先指定回调,不是执行回调 !!!

  4. promise.then() 返回的新 promise 的结果状态由什么决定

    由 then()指定的回调函数执行的结果决定

    image-20230724131226309

  5. promise 如何串连多个操作任务?

    image-20230724132123408

    image-20230724132438422

    为什么输出undefined?

    因为此时已经不是promise调用then方法了,success是一个字符串对象,不是promise对象

    这里要注意, 如果这个指定的回调函数(then里面的回调)执行后没有返回值, 返回的结果就是undifined(可能是一个成功的promise, 可能是一个失败的promise, 只不过成功或失败的值为undifined)

  6. 异常穿透

    image-20230724132619046

image-20230724133101246

  1. 如何中断Promise链

    image-20230724133136068

    image-20230724133413802

手写Promise

注: 我这里左边写错了,应该是

let p = new Promise((resolve,reject)=>{...})

image-20230724155916866

声明resolve, reject , 可以接收参数的

image-20230724160122840

resolve可以做两件事

1.改变对象状态(就是实例对象上的一个属性 promiseState)

2.设置结果值 promiseResult

image-20230722180646375

所以我们要给实例对象添加这两个属性

image-20230724160626971

接下来在resolve里面分别设置

image-20230724160650125

结果发现输出为:

image-20230724161023642

为什么数据和状态都没发生改变呢?

因为resolve里面的this指向为window, 因此我们要调整this指向

image-20230724161125752

现在

image-20230724161227478

接下来, 我们要实现

throw抛出异常来改变状态

(只要抛出异常状态就为失败了)

要通过try catch, 那么 try catch 写在哪里呢?

image-20230724170020575

image-20230724165432375

接下来, 需求是

promise状态只能修改一次

image-20230724170358958

以上代码, 正常的执行结果应该是resolve, 成功

但我们自己写的代码最后输出结果是reject, 因此我们要解决这个问题

怎么解决?

在走代码之前加一个判断即可

image-20230724170507027

then方法当中执行回调

在then方法中补全逻辑

image-20230724171009990

异步任务then的实现

首先要明白改变 promise 状态 和 指定回调函数(then或者catch) 谁先谁后, 我们知道, 是 指定回调---改变状态---执行回调

image-20230724172849283

这时, 是异步的, 接下来, then方法已经被指定了, 但状态还没改变

image-20230724172938586

callback定义

image-20230724173001304

接下来就是执行回调, 那就要在各自的回调函数里面执行

image-20230724173118063

image-20230724173133257

指定多个回调的实现

现在页面只会输出我画线的这句话

image-20230724173419970

正常情况控制台和alert都会出现OK

为什么我们的只出现了alert呢?

因为

image-20230724173606630

所以要把callback变成一个数组, 调用方法push 保存回调函数

//声明一个属性, 用于保存回调函数
    this.callbacks = [];

image-20230724174059387

所以接下来, 每一个回调都要执行, 就要遍历数组, 执行里面的每一个函数

image-20230724174005344

image-20230724174016481

同步修改状态then方法返回的结果

我们知道then方法应该返回一个 promise对象, 返回的结果是由then方法指定的回调函数的执行结果决定的

image-20230724131226309

所以我们要考虑这几种情况, 先去then方法里修改, 让其return 一个 Promise

image-20230724180840962

异步修改状态then方法返回的结果

image-20230725155124164

image-20230725155205670

image-20230725155309810

然后因为要抛出错误, 所以try catch

image-20230725155918382

完善then方法

then方法当中reject 我们没写全

image-20230725160505456

image-20230725160517198

看到上面的图, 我们发现这些函数都有try catch, 而且里面的内容很像, 所以我们要进行函数封装

image-20230725161743549

Async 和 await

async

  1. 函数的返回值为 promise 对象

  2. promise 对象的结果由 async 函数执行的返回值决定

    image-20230725162840981

await

The await operator is used to wait for a Promise and get its fulfillment value. It can only be used inside an async function or at the top level of a module.

  1. await 右侧的表达式一般为 promise 对象, 但也可以是其它的值

  2. 如果表达式是 promise 对象, await 返回的是 promise 成功的值

  3. 如果表达式是其它值, 直接将此值作为 await 的返回值

    注意

    1. await 必须写在 async 函数中, 但 async 函数中可以没有 await
    2. 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理

    在异步操作中,通常使用asyncawait来确保在异步操作完成之前不会继续执行后续代码。这样可以更好地处理异步操作的结果,例如处理登录成功或失败的情况,或者在获取数据后更新用户界面。

    使用asyncawait确实会让异步操作看起来像同步操作,因为它们会暂停函数的执行,等待异步操作完成。这是一种改进异步代码可读性的方式,因为它避免了回调嵌套和提供了更直观的控制流。但关键是它并没有真正将异步操作变成同步操作,而是使代码更易于理解和维护。

    在异步函数内部使用await会等待异步操作完成,但这不会阻塞整个应用程序的执行。其他未被await影响的代码仍然可以继续执行。这意味着你可以在等待异步操作的同时,执行其他非依赖于该异步操作结果的任务。这有助于应用程序保持响应性。

image-20230725164859966

async + await

案例一:

image-20230725170152983

案例二:

image-20230725170303126