8-js高级-6(promise)

2023-11-11

一 Promise 的理解和使用

1 Promise 是什么?

理解

抽象表达:

  • Promise 是一门新的技术(ES6 规范)
  • Promise 是 JS 中进行异步编程的新解决方案 (备注:旧方案是单纯使用回调函数)

具体表达:

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

promise 的状态改变

  1. pending 变为 resolved
  2. pending 变为 rejected

**说明: **

只有这 2 种, 且一个 promise 对象只能改变一次

无论变为成功还是失败, 都会有一个结果数据

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

promise 的基本流程

在这里插入图片描述

promise 的基本使用

  • 使用 1: 基本编码流程
  <script>
        // 1) 创建 promise 对象(pending 状态), 指定执行器函数
        const p = new Promise((resolve, reject) => {
            // 2) 在执行器函数中启动异步任务
            setTimeout(() => {
                const time = Date.now();
                // 3) 根据结果做不同处理
                if (time % 2 == 1) {
                    // 3.1) 如果成功了, 调用 resolve(), 指定成功的 value, 变为 resolved 状态
                    resolve("成功的值" + time);
                } else {
                    // 3.2) 如果失败了, 调用 reject(), 指定失败的 reason, 变为rejected 状态
                    reject("失败的值" + time);
                }
            }, 2000);
        });

        // 4) 能 promise 指定成功或失败的回调函数来获取成功的 vlaue 或失败的 reason
        p.then(
            // 成功的回调函数 onResolved, 得到成功的 vlaue
            value => console.log('成功的 value: ', value),
            // 失败的回调函数 onRejected, 得到失败的 reason
            reason => console.log('失败的 reason: ', reason)
        );
      
    </script>
  • 使用 2: 使用 promise 封装基于定时器的异步
    <script>

        function doDelay(time) {
            return new Promise((resolve, reject) => {

                setTimeout(() => {
                    const now = Date.now();
                    if (now % 2 == 1) {
                        resolve("成功的值" + now);
                    } else {
                        reject("失败的值" + now);
                    }
                }, time);
            });
        }

        const promise = doDelay(2000);
        promise.then(
            value => console.log('成功的 value: ', value),
            reason => console.log('失败的 reason: ', reason)
        );


    </script>
  • 使用 3: 使用 promise 封装 ajax 异步请求
    <script>

        //可复用的发 ajax 请求的函数: xhr + promise
        function promiseAjax(url) {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest()
                xhr.onreadystatechange = () => {
                    if (xhr.readyState !== 4) return
                    const { status, response } = xhr
                    // 请求成功, 调用 resolve(value)
                    if (status >= 200 && status < 300) {
                        resolve(JSON.parse(response))
                    } else { // 请求失败, 调用 reject(reason)
                        reject(new Error('请求失败: status: ' + status))
                    }
                }
                xhr.open("GET", url)
                xhr.send()
            })
        }
        promiseAjax('http://152.136.185.210:7878/api/hy66/home/data?type=pop&page=1')
            .then(
                data => {
                    console.log('显示成功数据', data)
                },
                error => {
                    alert(error.message)
                }
            )

    </script>

2 为什么要用 Promise?

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

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

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

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

  1. 什么是回调地狱?

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

  2. 回调地狱的缺点?

    不便于阅读 不便于异常处理

  3. 解决方案?

    promise 链式调用

  4. 终极解决方案?

    async/await

    <script>
        // 成功的回调函数
        function successCallback(result) {
            console.log("声音文件创建成功: " + result);
        }
        // 失败的回调函数
        function failureCallback(error) {
            console.log("声音文件创建失败: " + error);
        }
        /* 1.1 使用纯回调函数 */
        createAudioFileAsync(audioSettings, successCallback, failureCallback)

        /* 1.2. 使用 Promise */
        const promise = createAudioFileAsync(audioSettings);
        setTimeout(() => {
            promise.then(successCallback, failureCallback);
        }, 3000);

        /*
        2.1. 回调地狱
        */
        doSomething(function (result) {
            doSomethingElse(result, function (newResult) {
                doThirdThing(newResult, function (finalResult) {
                    console.log('Got the final result: ' + finalResult)
                }, failureCallback)
            }, failureCallback)
        }, failureCallback)
        /*
        2.2. 使用 promise 的链式调用解决回调地狱
        */
        doSomething().then(function (result) {
            return doSomethingElse(result)
        }).then(function (newResult) {
            return doThirdThing(newResult)
        }).then(function (finalResult) {
            console.log('Got the final result: ' + finalResult)
        }).catch(failureCallback)
        /*
        2.3. async/await: 回调地狱的终极解决方案
        */
        async function request() {
            try {
                const result = await doSomething()
                const newResult = await doSomethingElse(result)
                const finalResult = await doThirdThing(newResult)
                console.log('Got the final result: ' + finalResult)
            } catch (error) {
                failureCallback(error)
            }
        }

    </script>

如何使用 Promise?

API

  1. Promise 构造函数: Promise (excutor) {}
    • executor 函数: 执行器 (resolve, reject) => {}
    • resolve 函数: 内部定义成功时我们调用的函数 value => {}
    • reject 函数: 内部定义失败时我们调用的函数 reason => {}
    • 说明: executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行
  2. Promise.prototype.then 方法: (onResolved, onRejected) => {}
    • onResolved 函数: 成功的回调函数 (value) => {}
    • onRejected 函数: 失败的回调函数 (reason) => {}
    • 说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调
    • 返回一个新的 promise 对象
  3. Promise.prototype.catch 方法: (onRejected) => {}
    • onRejected 函数: 失败的回调函数 (reason) => {}
    • 说明: then()的语法糖, 相当于: then(undefined, onRejected)
  4. Promise.resolve 方法: (value) => {}
    • value: 成功的数据或 promise 对象
    • 说明: 返回一个成功/失败的 promise 对象
    • 说明: 当value为失败的promise对象时则返回一个失败的promise
  5. Promise.reject 方法: (reason) => {}
    • reason: 失败的原因
    • 说明: 返回一个失败的 promise 对象
  6. Promise.all 方法: (promises) => {}
    • promises: 包含 n 个 promise 的数组
    • 说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就 直接失败
  7. 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 状态和指定回调函数谁先谁后?
    • 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
    • 如何先改状态再指定回调?
      • 在执行器中直接调用 resolve()/reject()
      • 延迟更长时间才调用 then()
    • 什么时候才能得到数据?
      • 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
      • 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
  4. promise.then()返回的新 promise 的结果状态由什么决定?
    • 简单表达: 由 then()指定的回调函数执行的结果决定
    • 详细表达:
      • 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
      • 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
      • 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
  5. promise 如何串连多个操作任务?
    • promise 的 then()返回一个新的 promise, 可以开成 then()的链式调用
    • 通过 then 的链式调用串连多个同步/异步任务
  6. promise 异常传透?
    • 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,
    • 前面任何操作出了异常, 都会传到最后失败的回调中处理
  7. 中断 promise 链?
    • 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
    • 办法: 在回调函数中返回一个 pendding 状态的 promise 对象

二 async 与 await

定义

async 是异步的意思,await则可以理解为 async wait。所以可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行

async作为一个关键字放在函数前面,表示该函数是一个异步函数,异步函数意味着该函数的执行不会阻塞后面代码的执行;而 await 用于等待一个异步方法执行完成;

async/await的作用就是使异步操作以同步的方式去执行

1 关于async

async的用法,语法很简单,在函数前面加上async关键字,表示函数是异步的。

 async function timeout() {
     return 'hello world!'
 }

只有一个作用,他的调用会返回一个promise对象。

那怎么调用呢?async 函数也是函数,平时我们怎么使用函数就怎么使用它,直接加括号调用就可以了,为了表示它没有阻塞它后面代码的执行,我们在async 函数调用之后加一句console.log;

 async function timeout() {
     return 'hello world!'
 }
 timeout()
 console.log('我虽然在后面,但是先执行')

打印结果:

在这里插入图片描述

发现 timeout() 函数虽然调用了,但是没打印 hello world!; 先不要着急, 看一看timeout() 返回了什么? 把上面的 timeout() 语句改为console.log(timeout())

打印结果:

在这里插入图片描述

原来async 函数返回的是一个promise 对象,并且Promise还有state和result,如果async函数中有返回值,当调用该函数时,内部会调用Promise.resolve()方法把它转化成一个promise对象作为返回,但如果timeout函数内部抛出错误呢? 那么就会调用Promise.reject() 返回一个promise 对象

async function timeout() {
    throw new Error('rejected');
}
console.log(timeout());

就会调用Promise.reject() 返回一个promise 对象

在这里插入图片描述

那么要想获取到async 函数的执行结果,就要调用promise的then 或 catch 来给它注册回调函数

继续修改代码

  	async function timeout() {
      return 'hello world!'
    }

    timeout().then(val => {
      console.log(val)
    })

    console.log('我虽然在后面,但是先执行')

在这里插入图片描述

我们获取到了"hello world!', 同时timeout的执行也没有阻塞后面代码的执行,和我们刚才说的一致。

如果async 函数执行完,返回的promise 没有注册回调函数,比如函数内部做了一次for 循环,你会发现函数的调用,就是执行了函数体,和普通函数没有区别,唯一的区别就是函数执行完会返回一个promise 对象

   async function timeout () {
      for (let index = 0; index < 3; index++) {
        console.log('async', +index)
      }
    }
    console.log(timeout())
    console.log('outer')

在这里插入图片描述

另外,async函数返回一个promise对象,下面两种方法是等效的

// 方法1
function f() {
    return Promise.resolve('TEST');
}
// asyncF is equivalent to f!

// 方法2
async function asyncF() {
    return 'TEST';
}

2 关于await

await 到底在等啥?

async 关键字差不多了,最重要的就是async函数的执行会返回promise对象,并且把内部的值进行promise的封装。如果promise对象通过then或catch方法又注册了回调函数,async函数执行完以后,注册的回调函数就会放到异步队列中,等待执行。

如果只是async,和promise差不多,但有了await就不一样了,await关键字只能放到async函数里面,await是等待的意思,那么它等待什么呢?它后面跟着什么呢?其实await不仅仅用于等Promise对象,还可以等任意表达式,所以await后面实际是可以接普通函数调用或者直接量的,不过我们更多的是放一个返回promise 对象的表达式。他等待的是promise对象执行完毕,并返回结果。

//所以下面这个示例完全可以正确运行
    function getSomething () {
      return 'something'
    }
    async function testAsync () {
      return Promise.resolve('hello async')
    }
    async function test () {
      const v1 = await getSomething()
      const v2 = await testAsync()
      console.log(v1, v2)
    }
    test()

await 等到了要等的,然后呢?

await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?

  1. 如果它等到的不是一个Promise对象,那么await表达式的运算结果就是它等到的东西。
  2. 如果它等到的是一个Promise对象,await就忙起来了,它会阻塞函数后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果。

async/await 帮我们干了啥?

做个简单的比较
现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写

function takeLongTime () {
      return new Promise(resolve => {
        setTimeout(() =>
          resolve('long_time_value'), 1000
        )
      })
    }
takeLongTime().then(val => {
   console.log(val, 'val')
})

如果改用 async/await 呢,会是这样

    function takeLongTime () {
      return new Promise(resolve => {
        setTimeout(() =>
          resolve('long_time_value'), 1000
        )
      })
    }
     async function test () {
      let v = await takeLongTime()
      console.log(v, 'v')
    }
    test()

眼尖的已经发现 takeLongTime () 没有申明为async。实际上takeLongTime () 本身就返回Promise对象,加不加async结果都一样。

await 优势在于处理 then 链,使代码看起来像同步代码一样,下面是实例应用

现在写一个函数,让它返回promise 对象,该函数的作用是2s 之后让数值乘以2

// 2s 之后返回双倍的值
function doubleAfter2seconds (num) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(num * 2)
        }, 2000)
      })
    }

现在再写一个async 函数,从而可以使用await 关键字, await 后面放置的就是返回promise对象的一个表达式,所以它后面可以写上 doubleAfter2seconds 函数的调用

async function testResult() {
    let result = await doubleAfter2seconds(30);
    console.log(result); //2s后打印60
}
testResult();

代码的执行过程

调用testResult 函数,它里面遇到了await, await 表示等待,代码就暂停到这里,不再向下执行了,它等待后面的promise对象执行完毕,然后拿到promise resolve 的值并进行返回,返回值拿到之后,它继续向下执行。具体到 我们的代码, 遇到await 之后,代码就暂停执行了, 等待doubleAfter2seconds(30) 执行完毕,doubleAfter2seconds(30) 返回的promise 开始执行,2秒 之后,promise resolve 了, 并返回了值为60, 这时await 才拿到返回值60, 然后赋值给result, 暂停结束,代码继续执行,执行 console.log语句。

就这一个函数,我们可能看不出async/await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?

async function testResult() {
    let first = await doubleAfter2seconds(30);
    let second = await doubleAfter2seconds(50);
    let third = await doubleAfter2seconds(30);
    console.log(first + second + third);
}
testResult()

6秒后,控制台输出220, 我们可以看到,写异步代码就像写同步代码一样了,再也没有回调地域了。

这里强调一下,当js引擎在等待promise.resolve的时候,他并没有真正的暂停工作,它可以处理其他的一些事情,如果我们在testResult函数后面继续执行其他代码,比如console.log一下,会发现console.log代码先执行。

async function testResult() {
    let first = await doubleAfter2seconds(30);
    let second = await doubleAfter2seconds(50);
    let third = await doubleAfter2seconds(30);
    console.log(first + second + third);
}
testResult()
console.log('我先执行!!!')

先输出 “我先执行!!!”,6s后输出计算结果。

在这里插入图片描述

3 举例

当遇到 await 时,会阻塞函数内部处于它后面的代码(而非整段代码),去执行该函数外部的同步代码;当外部的同步代码执行完毕,再回到该函数执行剩余的代码。并且当 await 执行完毕之后,会优先处理微任务队列的代码。

 <script>
        async function fn1() {
            console.log(1)
            const value = await fn2() // fn2进入微任务队列等待执行
            console.log(value)
            console.log(2) // 阻塞
        }
        async function fn2() {
            console.log('fn2')
            return 666;
        }
        fn1()
        console.log(3)
    </script>

    // 结果:1 fn2 3 666 2
    // 痛点:
    // 执行fn2()后返回一个成功状态且值为666的promise,该promise的then的回调进入微任务队列等待执行
    // await 会阻塞它下面的代码,先执行 async 外面的同步代码
    // 同步代码执行完后,执行异步任务(当前为微任务队列中得promise的then的回调)
    // 释放了async函数中的await的阻塞,使得其后的代码得到执行

4 总结:

  1. async 函数
    1)函数的返回值为Promise对象
    2)Promise对象的结果由async函数执行的返回值决定
  2. await 表达式
    1)正常情况下,await右侧的表达式一般为 promise对象 , 但也可以是其它的值
    2)如果表达式是promise对象,await就忙起来了,它会阻塞函数后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果。
    3)如果表达式是其它值, 直接将此值作为await的返回值
  3. asyncawait基于promise的。使用async的函数将会始终返回一个 promise 对象。这一点很重要,要记住,可能是你遇到容易犯错的地方。
  4. 在使用await的时候我们只是暂停了函数,而非整段代码。这里经常会是容易犯错的地方。
  5. async和await是非阻塞的
  6. 仍然可以使用 Promise,例如Promise.all(p1, p2, p3).,接受一个数组作为参数,p1、p2、p3 都是 Promise 实例,如果不是,就会先调用 Promise .resolve方法,将参数转为 Promise 实例,再进一步处理。只要 p1、p2、p3 之中有一个被 rejected,整个状态就变成 rejected。
  7. 注意
    1)await必须写在async函数中, 但async函数中可以没有await
    2)如果await的promise失败了, 就会抛出异常, 需要通过try…catch来捕获处理
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

8-js高级-6(promise) 的相关文章

随机推荐

  • 《吃透 MQ 系列》之Kafka精妙的高性能设计(下篇)

    在 上一篇文章 中 指出了高性能设计的两个关键维度 计算和 IO 可以将它们理解成 道 同时给出了 Kafka 高性能设计的全景图 可以理解成 术 图 1 Kafka 高性能设计的全景图 这篇文章将继续对存储消息和消费消息的 8 条高性能设
  • 基于C语言的栈

    基于王道数据结构 include
  • 开源静态代码检测工具Splint

    如果想用一个有效的工具察看C C 源代码中的错误 遗漏 不确定的构建过程 以及移植问题等等 你应该来看看Lint 可以把Lint当成一个编译器 除了不产生代码之外 对于错误和警告的报告来说已经非常足够了 通常 一个C C 的编译器假设程序是
  • Java实现人脸登录、注册等功能【完整版】

    推荐 前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住分享一下给大家 点击跳转到网站 前言 这段时间由于学校实行静态化管理 寝室门和校门都是用了人脸识别的装置 每次经过都会激发我的好奇心 也想自己搞一个人脸识别玩玩 随着开
  • python机器学习 transform,fit_transform

    首先使用transfer StandardScaler 来实例化一个转换器 我们要对训练集和测试集进行相同的归一化 标准化处理 先处理训练集 x train transfer fit transform x train fit transf
  • 【纯干货】学python的,这些能快速月入过万的兼职途径,你不会还不知道吧

    我想辞职 在这个疫情当下的时代 许多打工人都有过这么一个想法 或许是因为工作待遇 亦或许是其他原因 但是却仍然屹立在工位上 有的甚至天天喊辞职 月月拿满勤 这是为什么呢 因为他们虽然无数次筹谋辞职 却也无数次的担心裸辞之后的压力 而作为平平
  • Hyper Terminal 配置体验分享

    Hyper Terminal 简介 Hyper is an Electron based terminal Built on HTML CSS JS Fully extensible 以上内容来自Hyper Terminal官网对该终端的介
  • 基于卷积神经网络-门控循环单元(CNN-GRU)多输入多输出预测,CNN-GRU回归预测。

    清空环境变量 warning off 关闭报警信息 close all 关闭开启的图窗 clear 清空变量 clc 清空命令行 导入数据 res xlsread 数据 xlsx 数据分析 num size 0 8 训练集占数据集比例 ou
  • vue解决弹出图片显示在弹框下方

    弹出的图片显示在弹框下面怎么办 问题来源 问题分析 解决方法 问题来源 在写前端vue项目时 在用到ele的 el image 这个组件时 有时会出现图片显示在弹框即dialog下面 后面发现是因为el image组件 默认的z index
  • 【ffmpeg基础】ffmpeg的下载安装

    一 ffmpeg的下载 1 ffmpeg github下载路径 https github com FFmpeg FFmpeg git 在ffmpeg的github上可以下载任意版本的源码 比如最新的matser上的源码 以及各个分支上 如f
  • unity 屏幕虚拟键盘

    工作上碰到许多程序需要用到键盘输入功能 调用的电脑自带键盘使用也不方便 自己写的一个键盘工具 功能 键盘大小写状态监测 设置了输入法提示词位置的定位 定位根据屏幕分辨率设置 故编辑器模式下位置有偏移 可自行调整 工具连接 https dow
  • rocketMq消息队列原生api使用以及rocketMq整合springboot

    rocketMq消息队列 文章目录 rocketMq消息队列 一 RocketMQ原生API使用 1 测试环境搭建 2 RocketMQ的编程模型 3 RocketMQ的消息样例 3 1 基本样例 3 2 顺序消息 3 3 广播消息 3 4
  • Friend-Graph HDU - 6152 签到题 暴力遍历

    Friend Graph HDU 6152 题意 给你n个人 告诉你他们之间的关系 如果有三个以上的人互相不认识或者互相认识 就认为这个团队是 Bad Team 反之输出 Great Team 我的想法就是暴力搜索 用一个二维数组保存每个人
  • 利用硬件实现矩阵乘法加速

    对于绝大多数程序员来说 优化程序往往是在算法方面 但了解一定的计算机硬件知识后 可以隐式地优化程序 下面以矩阵乘法为例 探讨计算机硬件在程序优化中的作用 原理 学过计算机组成原理的都知道 CPU访问内存的速度比CPU计算速度慢得多 为了解决
  • WKWebView设置请求头HTTPHeaderField

    WKWebView HTTPHeaderField WKWebView的请求头添加字段 系统的NSMutableHTTPURLRequest类提供了获取HTTP请求的请求头 HTTPHeader 和设置 添加HTTP请求的请求头的API p
  • 龙书D3D11章节习题答案(第四章)

    以下答案仅供参考 有错欢迎留言 Chapter 4 Direct3D Initialzation 1 Modify the previous exercise solution by disabling the ALT ENTER func
  • DVWA XSS总结

    笔者对该靶场所需的相关知识进行了总结 拓展 供大家学习参考 XSS 漏洞学习 DVWA XSS Reflected low 未进行过滤 构造payload medium 过滤规则 把 lt script gt 用str replace 函数
  • Java类加载

    1 JAVA类装载器在装载类的时候是按需加载的 只有当一个类要使用 使用new 关键字来实例化一个类 的时候 类加载器才会加载这 个类并初始化 类Main java 代码 publicclass Main publicstaticvoid
  • STM32—CAN通信

    文章目录 一 CAN通信简介 1 1 CAN简介 1 2 CAN协议特点 1 3 CAN通信的帧类型 1 4 数据帧结构 1 5 CAN的位时序 1 6 CAN的仲裁功能 二 STM32F1的CAN 2 1 bxCAN简介 2 2 bxCA
  • 8-js高级-6(promise)

    一 Promise 的理解和使用 1 Promise 是什么 理解 抽象表达 Promise 是一门新的技术 ES6 规范 Promise 是 JS 中进行异步编程的新解决方案 备注 旧方案是单纯使用回调函数 具体表达 从语法上来说 Pro