promise和

90 阅读7分钟

回调地域

存在异步任务的代码,不能保证能按照顺序执行,如果我们需要代码顺序执行,要怎么写呢

setTimeout(function () {  //第一层
            console.log(111);
            setTimeout(function () {  //第二程
                console.log(222);
                setTimeout(function () {   //第三层
                    console.log(333);
                }, 1000)
            }, 2000)
        }, 3000)

这种回调函数的层层嵌套,就叫做回调地狱。回调地狱会造成代码可复用性不强,可阅读性差,可维护性(迭代性差),扩展性差等等问题。

异步困境

  • 众所周知,js是单线程的,耗时操作都是交给浏览器来处理,等时间到了从队列中取出执行,设计到事件循环的概念,笔者也分享过,可以看以下,理解了可以更好的理解promise

  • 我以一个需求为切入点,我模拟网络请求(异步操作)

    • 如果网络请求成功了,你告知我成功了
    • 如果网络请求失败了,你告知我失败了

傻瓜做法

function requestData(url) {
  setTimeout(() => {
    if (url === 'iceweb.io') {
      return '请求成功'
    }
    return '请求失败'
  }, 3000)
}

const result = requestData('iceweb.io')
//先执行完了 再执行的异步 所以输出了undefined
console.log(result) //undefined

首先当我执行requestData函数,开始执行函数。遇到了异步操作不会阻塞后面代码执行的,因为js是单线程的,所以你写的return成功或者失败并没有返回给requestData,那我这个函数中,抛开异步操作,里面并没有返回值,所以值为undefined

早期正确做法

function requestData(url, successCB, failureCB) {
  setTimeout(() => {
    if (url === 'iceweb.io') {
      successCB('我成功了,把获取到的数据传出去', [{name:'ice', age:22}])
    } else {
      failureCB('url错误,请求失败')
    }
  }, 3000)
}

//3s后 回调successCB 
//我成功了,把获取到的数据传出去 [ { name: 'ice', age: 22 } ]
requestData('iceweb.io', (res, data) => console.log(res, data), rej => console.log(rej))

//3s后回调failureCB
//url错误,请求失败
requestData('icexxx.io', res => console.log(res) ,rej => console.log(rej))
  • 早期解决方案都是传入两个回调,一个失败的,一个成功的。那很多开发者会问这不是挺好的吗?挺简单的,js中函数是一等公民,可以传来传去,但是这样太灵活了,没有规范。

  • 如果使用的是框架,还要阅读一下框架源码,正确失败的传实参的顺序,如果传参顺序错误这样是非常危险的。

Promise

promise本身只是一个容器,真正异步的是它的两个回调resolve()和reject()

promise本质 不是控制 异步代码的执行顺序(无法控制) , 而是控制异步代码结果处理的顺序

  • promise对象有三个状态:pending(进行中),fulfilled(已成功),rejected(已失败)

  • 如何改变promise的状态

    • resolve(value): 如果当前是 pending 就会变为 resolved
    • reject(error): 如果当前是 pending 就会变为 rejected
    • 抛出异常: 如果当前是 pending 就会变为 rejected

    注意:一旦从进行状态变成为其他状态就永远不能更改状态了。

  • 当我们new一个promise,此时我们需要传递一个回调函数,这个函数为立即执行的,称之为(executor)

  • 这个回调函数,我们需要传入两个参数回调函数,reslove,reject(函数可以进行传参)

    • 当执行了reslove函数,会回调promise对象的.then函数
    • 当执行了reject函数,会回调promise对象的.catche函数

2.1立即执行

new Promise((resolve, reject) => {
  console.log(`executor 立即执行`)
})

解决回调地域

1.Ptomise链式解构

缺点:

写多了也相当于一种回调地域

function fn(){

        return new Promise(function (resolve, reject) {
        // 第一个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为成功
        // 第二个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为失败
        const timer = Math.ceil(Math.random() * 3000);
        console.log("买水");
        setTimeout(() => {
          if (timer > 2500) {
            reject('超时,所以买水失败');
          } else {
            resolve('超时,所以买水失败');
          }
        }, timer);
      });
      }



      //链式调用

      //当你在第一个then里边返回一个新的promise对象

      //然后你可以在第一个then的候面再次书写一个then

      fn().then((str) => {

        console.log("promise 1成功");

        return fn()

      }).then((str) => {

        console.log("promise 2成功");

        return fn()

      }).then((str) => {

        console.log("promise 3成功");

      }).catch(() => {

        console.log("promise 失败");

      });

2. async/await

一句话概括: 它就是 Generator 函数的语法糖,也就是处理异步操作的另一种高级写法

async语法如下:

async是异步的意思,await是等待的意思,async是声明一个异步函数,await是等待异步函数执行完毕;

  1. 函数前面使用async修饰,将函数变为异步
  2. 函数内部,promise操作使用await修饰
  • await 后面是promise对象, 左侧的返回值就是这个promise对象的then方法中的结果

  • await必须要写在async修饰的函数中,不能单独使用,否则程序会报错

await 关键字

async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。

await针对所跟不同表达式的处理方式:

  • Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。

  • 非 Promise 对象:直接返回对应的值

  • 异步函数中可以使用await关键字,普通函数不行

    • 这个promise状态变为fulfilled才会执行await后续的代码,所以await后面的代码,相当于包括在.then方法的回调中,如果状态变为rejected,你则需要在函数内部try catch,或者进行链式调用进行.catch操作

使用anync与awit解决回调地域

存在问题 promise状态变为fulfilled才会执行await后续的代码,所以await后面的代码,相当于包括在.then方法的回调中,如果状态变为rejected

function fn(){

        return new Promise(function (resolve, reject) {
        // 第一个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为成功
        // 第二个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为失败
        const timer = Math.ceil(Math.random() * 3000);
        console.log("买水");
        setTimeout(() => {
          if (timer > 2500) {
            reject('超时,所以买水失败');

          } else {
            resolve('成功');
          }
        }, timer);
      });
      }
      
      newfn()
      async function newfn(){
        const r1 = await fn()
        cosole.log('第一次',r1)
        const r2 = await fn()
        console.log('第一次',r2)
        const r3 = await fn()
        console.log('第一次',r3)
      }

异步函数的异常处理

1.`try catch

function fn(){
        return new Promise(function (resolve, reject) {
        // 第一个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为成功
        // 第二个参数:内部的只是一个函数,调用之后可以将当前这个promise的状态设置为失败
        const timer = Math.ceil(Math.random() * 3000);
        console.log("买水");
        setTimeout(() => {
          if (timer > 100) {
            reject('超时,所以买水失败');
          } else {
            resolve('成功');
          }
        }, timer);
      });
      }

      newfn()
      async function newfn(){
        try{ //try抛出错误之后,就不会执行这条语句
          const r1 = await fn()
          console.log('第一次',r1)
          const r2 = await fn()
          console.log('第二次',r2)
          const r3 = await fn()
          console.log('第三次',r3)

        }catch(error){
          console.log(error)//catch语句能捕获到错误信息
        }
        
        console.log('我是一个字符串')//try接收到报错后 不会影响后续输出

      }

封装Promise 待写

promise对象方法

finally

每次都会执行一次

//1.promise 对象上的方法
      const res = fn()
      res.then(()=>{
        console.log('成功')
      }).catch(()=>{
        console.log('失败')
      }).finally(()=>{
        console.log('每一次都会执行(不会考虑成功还是失败)')
      })

promise构造函数方法

all 可以实现并行处理

必须都成功才算成功


//all 必须都成功才算成功
      Promise.all([fn(),fn()]).then(()=>{
        console.log('都成功')
      }).catch(()=>{
        console.log('有一个失败就是失败')
      })

race

执行速度最快的是对的 就是对的

//race 执行速度最快的是对的 就是对的
      Promise.race([fn(),fn()]).then(()=>{
        console.log('都成功')
      }).catch(()=>{
        console.log('有一个失败就是失败')
      })