什么是Promise?

492 阅读7分钟

这是我参与 8 月更文挑战的第 5 天,活动详情查看: 8月更文挑战

Promise

  • 在ES2015(ES6)中标准化的功能,中文名(暂定)“期约”,用于给异步编程提供一种书写更加合理,功能更强的统一的解决方案

同步与异步

  • JavaScript中存在同步与异步的概念。
  • 常见的异步任务有:事件、定时器、Ajax
  • 同步任务总是顺序执行,而异步任务执行结果与书写顺序无关
    • 例如:定时器结束时间取决于延迟时间;Ajax的响应成功时间取决于数据量和网速
  • 异步任务需要通过回调函数进行结果处理。

总结

  • JavaScript存在同步与异步的概念
  • 同步代码顺序执行
  • 异步代码与顺序无关,需要通过回调函数进行处理

Promise简介

  • 问题:如果存在多个具有依赖关系的异步任务,该如何处理?
  • 解决代码如下图,此时出现了“回调地狱”的问题

多个具有依赖关系的异步任务.png

Prmoise基础

  • Promise是一个ES6提供的类,使用时需要创建一个实例对象,指代我们需要进行的一个(异步)操作。
  • 创建完毕,这时promise处于一个“待定状态”,最终结果可能为“成功状态”或“失败状态”
  • 特点:结果明确后无法更改
  • 回到操作:那么对应操作中,利于一个Ajax请求,当请求成功或失败时,可以通过相应的“状态”进行执行结果的判断,从而进行相应的操作
  • 小结:Promise对象的操作就是通过状态变化上来触发对应的回调函数。

Promise使用方式

  • 基础如图所示

Promise基础.png

  • 步骤1:创建实例用于存储异步操作
  • 步骤2:参数为函数,在创建实例时同步执行
  • 步骤3:函数有两个参数,都是函数,调用时可决定Promise对象的状态
    • resolve(data) 传递成功时的结果
    • reject(err) 传递失败时的错误信息
  • 注意:由于Promise对象的结果明确后无法更改,所以两个函数在逻辑中只能有一个被执行。
const promise = new Promise(function (){
  // 传递成功,返回100
  resolve(100)
})
  • 步骤4:当Promise对象的状态确定时,会触发对应的回调函数进行处理,需要通过Promise对象的方法指定这些回调函数
    • then() 成功时的处理函数
    • catch() 失败时的处理函数
  • 代码演示
// 创建Promise实例
const promise = new Promise(function (){
  resolve(100)
})
// 回调函数处理
promise.then(function (value){
  console.log("成功了,数据为:",value)
}).catch (function (err){
  console.log("失败了,数据为:",err)
}

Promise练习:封装Ajax函数

  • 回顾原生Ajax,请看这篇文章Ajax基础和手写封装Ajax函数

  • 原生发送Ajax步骤
    • 1.创建XMLHttpRequest类型的对象
    • 2.准备发送,打开与一个网址之间的连接
    • 3.执行发送动作
    • 4.指定xhr状态变化事件处理函数
  • 具体实现代码
function ajax (url) {
  return new Promise(function (resolve,reject) {
    // 创建XMLHttpRequest类型的对象
    const xhr = new XMLHttpRequest()
    // 准备发送,打开与一个网址之间的连接
    xhr.open('GET',url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if(this.status === 200){
        // 接收响应数据,标记状态为成功,并传递给then中的回调处理
        resolve(this.response)
      }else {
        // 失败时更改状态,并传递错误信息
        reject(new Error(this.statusText))
      }
    }
    // 执行发送动作
    xhr.send()
  })
}

ajax(successUrl).then(function (res) {
  console.log(res)
}).catch(function (error) {
  console.log(error)
})
  • 如果仅仅是这样使用,请求多了,依然会出现嵌套的问题,这时要注意使用方式,不要嵌套书写,而是利用Promise的链式调用进行操作
// 错误写法
ajax(url).then(function (res) {
  console.log(res)
  ajax(url).then(function (res) {
    console.log(res)
    ajax(url).then(function (res) {
      console.log(res)
  })
  })
})

链式调用

  • then()调用的返回值为一个新的Promise对象,如果要在一次异步操作后再执行下一个异步操作,应当通过这个新的Promise对象进行处理,这样就可以避免回调地狱
  • 正确写法
// 正确写法
ajax(url)
  .then(function (res){
    console.log(1)
  })
  .then(function (res){
    console.log(2)
  })
  .then(function (res){
    console.log(3)
  })
  • then()调用的返回值为一个新的Promise对象,如果要在一次异步操作后再执行下一个异步操作,应当通过这个新的Promise对象进行处理,这样就可以避免回调地狱
  • 正确写法
// 正确写法
ajax(url)
  .then(function (res){
    console.log(1)
  })
  .then(function (res){
    console.log(2)
  })
  .then(function (res){
    console.log(3)
  })
  • 在then()中通过return返回Promise对象会被设置为then()的返回值
ajax(url1)
  .then(function (res) {
    console.log(res,1)
    // 返回的promise对象,设置给下一个then()
    return ajax(url2)
  }).then(function (res) {
    console.log(res,2)
    return ajax(url3)
  }).then(function (res) {
    console.log(res,3)
    return ajax(url4)
  })
  • 如果返回的是普通值,则会包裹在默认的Promise对象中,传递给then方法,不传时默认为undefined
ajax(url1)
  .then(function (res) {
    console.log(res,1)
    // 返回的promise对象,设置给下一个then()
    return ajax(url2)
  }).then(function (res) {
    console.log(res,2)
    // 返回:普通值
    return [1,2,3]
  }).then(function (res) {
    console.log(res,3) // 输出:[1,2,3],3
    return ajax(url4)
  })

总结

  • Promise本质上就是通过回调函数定义异步任务结束后所需要执行的任务,只不过回调是通过then()进行设置,由于then()可以进行链式调用,所以避免了层层嵌套的书写方式。

Async函数

  • Promise的出现解决了异步操作回调地狱的问题,但书写方式中函数出现了大量的回调,虽然没有嵌套,但可读性远不及同步代码清晰
  • 为了让异步代码书写更加简洁、可读性更强,在ES2017中提出了Async函数(异步函数),用于简化Promise的书写方式,让代码格式更加接近同步代码写法
  • Async是异步编程的新标准,是一种用于改进异步书写的语法糖,本质还是Promise对象
  • 使用方式:
    • 在任意函数前设置async关键字,将函数设置为async函数(异步函数)
    • 在async函数内调用返回了promise对象的函数,并在调用前设置await
      • 此时,调用的返回值会被await设置为resolve传递的数据,即成功的结果
      • 失败时报错(需通过try...catch处理)
      • 注意:await只能出现在async函数中
  • 演示代码
async function fn () {
  const data1 = await ajax(url)
  console.log(1,data1)
  const data2 = await ajax(url)
  console.log(2,data2)
}
fn()

Try...catch

  • 用于进行异常捕获(报错处理),当程序代码可能出现报错时,可以通过try...catch进行异常捕获
  • 为了程序的健壮性,应当利用try...catch对async/await进行处理
  • 演示代码
function ajax (url) {
  return new Promise(function (resolve, reject) {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      } 
    }
    xhr.send()
  })
}

async function fn () {
  const data1 = await ajax(successUrl)
  console.log(1, data1)
  const data2 = await ajax(successUrl)
  console.log(2, data2)
// 例如:假设为避免请求3失败导致后续代码⽆法执⾏,可通过 try...catch 处理
  try {
    const data3 = await ajax(failUrl)
    console.log(3, data3)
  } catch (error) {
    console.log('进⾏补救处理')
  }
  // 即使请求3失败,后续请求也可以继续执⾏。
  const data4 = await ajax(successUrl)
  console.log(4, data4)
}
fn()

扩充

  • Promise可以解决“回调地狱”问题。
  • 面试题:什么是Promise?
    • 异步操作有一个“回调地狱”的问题
    • 出现的时候:存在多个异步调用并且异步调用之间有依赖关系时,就会形成“回调地狱”的情况
    • 为了解决这个问题,在ECMAScript2015(ES6)提出了Promise语法,专门来做异步的操作。
    • 希望通过Promise将异步调用的功能写的更加优雅
  • async和await
  • 异步处理,同步写法
  • 推荐大家看看大佬总结的async和await

不可预知的try catch

  • 允许对程序中出错的,进行补救。