8-js高级-6(promise)

一 Promise 的理解和使用

1 Promise 是什么?

理解

抽象表达:

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

那什么是异步编程?
众所周知,js语言是单线程机制。所谓单线程就是按次序执行,执行完一个任务再执行下一个。但是不影响存在同步和异步的两种操作,这两种操作做事情其实都是在一条流水线上(单线程),只是这两种操作在单线程上的执行顺序不一样罢了!当js触发到异步任务时,会将异步任务交给浏览器处理,当执行有结果时,会把异步任务的回调函数插入待处理队列的队尾!
我们通俗的去解释一下我们的异步:异步就是从主线程发射一个子线程来完成任务,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的.

具体表达:

  • 从语法上来说: 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>

3 如何使用 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来捕获处理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值