es6+——Promise篇

134 阅读10分钟

Promise

  • Promise是ES6中的新特性,是一个异步编程的一种解决方案,可以获取异步操作成功或失败的结果

  • Promise译为承诺,即承诺一段时间后会提供一个状态(成功、失败)

Promise的三种状态

  • pending:初始状态,表示异步操作正在进行中
  • fulfilled:表示异步操作成功
  • rejected:表示异步操作失败

且Promise的状态不可逆,状态一旦改变,就不会再变。

Promise的作用

在promise出现之前,处理多个异步请求,为了拿到回调的结果,必须一层一层的嵌套

const fs = require('fs')

fs.readFile('./1.txt','utf-8',(err,data)=>{
    console.log(data)
    fs.readFile('./2.txt','utf-8',(err,data)=>{
        console.log(data)
    	fs.readFile('./3.txt','utf-8',(err,data)=>{
            console.log(data)
    		// 异步请求嵌套异步请求,形成回调地狱
		})
	})
})

回调地狱:一个异步请求套着一个异步请求,一个异步请求依赖于另一个的执行结果,使用回调的方式相互嵌套。

promise主要解决两个问题

  • 解决回调地狱。Promise 将嵌套调用改为链式调用,增加了可阅读性和可维护性。
  • 支持多个并发的请求,获取并发请求中的数据

注意:promise可以解决异步问题,但不能说promise本身是异步的

Promise基本使用

Promise是一个构造函数,通过new操作可以创建一个promise对象

const promise = new Promise((resolve,reject)=>{
    // 执行一些异步操作。 但本身代码块内是同步的
})

Promise的构造函数值接收一个参数(一个函数),并且这个函数需要两个参数(resolve,reject)

  • resovle:成功的回调函数,将状态从pending变为fulfilled
  • reject:失败的回调函数,将状态从pending变为rejected

一旦Promise 被 resolve 或 reject,不能再迁移至其他任何状态(即状态不可逆)。

const promise = new Promise((resolve, reject) => {
  readFile('./1.txt', 'utf-8', (err, data) => {
    if (err) {
      // reject('读取失败')
      reject(err)
    } else {
      // resolve('读取成功')
      resolve(data)
    }
    // console.log(promise)
  })
})
promise
  .then((data) => {
    console.log('data:', data)
  })
  .catch((err) => {
    console.log('err:', err.message)
  })
// 如果promise状态为成功(fulFilled),调用then
// 如果promise状态为失败(rejected),调用catch

then、catch

在Promise构造函数的原型上有then和catch方法

  • then:异步执行成功的回调函数
promise.then((data) => {
    console.log(data)
    // 隐式返回
    // return Promise.resolve(undefined)
})
  • catch:异步执行失败的回调函数
promise.carch((err) => {
    console.log(err.message)
    // 隐式返回
    // return Promise.resolve(undefined)
})

then的返回值

  • 返回了一个值,那么then返回的Promise是成功状态并将返回的值作为该成功状态的回调函数的参数值
  • 没有返回值,那么then返回的Promise是成功状态并该成功状态的回调函数的参数值为undefined
  • 抛出异常,那么then返回的Promise是失败状态,并且将抛出的错误作为失败状态的回调函数的参数值
  • 返回一个已经是成功状态的Promise,那么then返回的Promise也是成功状态并该成功状态的回调函数的参数值与返回的Promise中的相同
  • 返回一个已经是拒绝状态的Promise,那么 then返回的 Promise也会成为拒绝状态并该拒绝状态的回调函数的参数值与返回的Promise中相同
  • 返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。

将异步函数封装promise风格

  • 封装一个基于promise异步读取文件的函数readFile
function readFilePromise(path) {
  return new Promise((resolve, reject) => {
    readFile(path, 'utf-8', (err, data) => {
      if (err) {
        reject(err)
      }
      	resolve(data)
    })
  })
}

readFilePromise('./1.txt')
  .then((data) => {
    console.log(data)
  })
  .catch((err) => {
    console.log(err.message)
})

基本上所有的异步api都可以封装为promise的调用风格

链式调用

Promise的主要特点之一,可以解决回调地狱

原理 :then()方法会默认返回一个新的Promise对象,若没有设置返回值,则会返回一个成功状态的promise对象(值为undefined)

即:在上一个then方法中返回了一个新的Promise对象,让新生成的Promise对象在进行调用then方法处理异步操作。

// 异步的按顺序异常读取1.txt 2.txt 3.txt文件内容
const { readFile } = require('fs')
// 在异步的方式下,按顺序依次读取 1.txt 2.txt 3.txt
function readFilePromise(path) {
  return new Promise((resolve, reject) => {
    readFile(path, 'utf-8', (err, data) => {
      if (err) {
        reject(err)
      }
      resolve(data)
    })
  })
}

readFilePromise('1.txt')
  .then(
    (data) => {
      console.log(data)
      return readFilePromise('2.txt')
    }
  )
  .then(
    (data) => {
      console.log(data)
      return readFilePromise('3.txt')
    }
  )
  .then(
    (data) => {
      console.log(data)
    }
  )

由于封装的readFilePromise函数返回的是一个promise对象,所以可以继续调用then方法实现链式调用,正是promise的精髓所在

promiseify

nodejs中的util模块中的promiseify方法,可以将异步的api转为promise调用风格

const {promisify} = require('util')
const {readFile} = require('fs')
// 将readFile异步api转为promise风格
const readFilePromise = promiseify(readFile)

readFilePromise('./1.txt')
 .then((data) => {
    console.log(data)
  })
  .catch((err) => {
    console.log(err.message)
  })

Promise常用的静态方法

Promise.all()

Promise.add(arr)用于并行处理多个异步操作,传入参数为一个 Promise实例数组,返回一个新的Promise对象。

但调用then方法返回的data为数组类型。

作用:可以实现多个异步操作并发请求

const promise1 = readFilePromise('./1.txt')
const promise2 = readFilePromise('./2.txt')
const promise3 = readFilePromise('./3.txt')

Promise.all([promise2, promise1, promise3])
  .then((data) => {
    // 只有当所有异步操作都成功才执行then
    console.log(data) // [ '2222', '1111', '3333' ]
  })
  .catch((err) => {
    // 任意一个异步失败,都直接会触发catch
    console.log(err.message)
  })

注意Promise.all()的成功返回结果data为一个数组,返回数组中的顺序与传参数组中的顺序保持一致

应用场景:尤其在一个页面需要同时发送多个ajax请求时,为了提高请求效率可以采用并行请求,可以加快页面中资源的获取。

Promise.race()

Promise.race(arr),race可以译为竞赛,只会得到第一个完成的promise最终结果状态(成功或失败),传入参数为一个Promise实例化数组,返回一个新的Promise对象。

const promise1 = readFilePromise('./1.txt')
const promise2 = readFilePromise('./2.txt')
const promise3 = readFilePromise('./3.txt')

Promise.race([promise1, promise2, promise3])
  .then((data) => {
    // 所有异步操作都成功才执行,但只获取第一个完成的结果
    console.log(data) // 可能为1111/2222/3333
  })
  .catch((err) => {
    // 任意有一个异步失败,都直接触发catch
    console.log(err.message)
  })

注意:最终只会得到第一个完成的最终结果。

all与race的区别:

  • Promise.all()并行执行,获取所有的异步操作结果
  • Promise.race()并行执行,获取异步最快完成的第一个的结果

Promise.allSettled()

传入参数为一个Promise实例化数组,该方法会返回一个promise对象

当所有promise都已敲定(每一个promise都成功或拒绝)

then中的data最终得到一个对象数组,里面的对象对应每一个传入的promise结果({status:'', value:''})

const promise1 = readFilePromise('./1.txt')
const promise2 = readFilePromise('./2.txt')
const promise3 = readFilePromise('./3.txt')

Promise.allSettled([promise2, promise1, promise3])
  .then((data) => {
    console.log(data)
  })
  .catch((err) => {
    console.log(err.message)
  })
/* 
  [
  { status: 'fulfilled', value: '2222' },
  { status: 'fulfilled', value: '1111' },
  { status: 'fulfilled', value: '3333' }
]
*/

Promise.any()

传入参数为一个Promise实例化数组,只会返回一个Promise对象

最终then方法得到的data为第一个成功状态的promise的值。

该方法用于获取首个成功的 promise 的值。只要有一个 promise 为成功状态,那么此方法就会提前结束。

const promise1 = readFilePromise('./1-err.txt')
const promise2 = readFilePromise('./2.txt')
const promise3 = readFilePromise('./3-err.txt')

Promise.any([promise2, promise1, promise3])
  .then((data) => {
    console.log(data) // 2222
  })
  .catch((err) => {
    console.log(err.message)
  })

Promise.resolve()与Promise.reject()

实际应用中,我们可以使用Promise.resolvePromise.reject 方法,用于将于将非Promise实例包装为Promise实例

Promise.resolve({a:1})
Promise.reject({a:1})
// 等价于
new Promise((resolve,reject) => {
    resolve({a:1})
})
new Promise((resolve,reject) => {
    reject({a:1})
})

Promise.resolve()的参数

  • 一个Promise实例 ,直接返回当前实例
  • 无参数或普通数据对象,直接返回一个fulfilled状态的Promise对象
  • 一个thenable对象(thenable对象指的是具有then方法的对象),转为 Promise 对象,并立即执行thenable对象的then方法。
function Promise_resolve(value) {
  // 一个Promise实例 ,直接返回当前实例
  if (value instanceof Promise) {
    return value
  }
  return new Promise((resolve, reject) => {
    // 一个thenable对象,转为 Promise 对象,并立即执行thenable对象的then方法。
    if (value && value.then && typeof value.then === 'function') {
      setTimeout(() => {
        value.then(resolve, reject)
      })
    } else {
      // 无参数或普通数据对象,直接返回一个`fulfilled`状态的Promise对象
      resolve(value)
    }
  })
}

Promise.resolve()与Promise.reject()的区别

Promise.reject始终返回一个状态的rejected的Promise实例,而Promise.resolve的参数如果是一个Promise实例的话,返回的是参数对应的Promise实例,所以状态不一定。

Promise的三种行为

值穿透

then()方法返回一个新的Promise对象

值穿透(value progagation):当Promise对象的then方法没有返回值(返回undefined),会默认返回一个成功状态(fulfilled)的新Promise对象。这个过程称为值穿透

// 值穿透:then方法没有返回值(undefined)时,会默认返回一个新的状态为成功(fulfilled)的Promise对象。
Promise.resolve('ok')
  .then((data) => {
    console.log(1, data)
  })
  .then((data) => {
    console.log(2, data)
  })
  .then((data) => {
    console.log(3, data)
  })
  .catch((err) => {
    console.log(err)
  })

/* 
    1 ok
    2 undefined
    3 undefined
  */

异常穿透

catch()方法也会返回一个新的Promise对象

异常穿透(error propagation):当Promise对象的catch方法没有返回值(返回undefined),会默认返回一个成功状态(fulfilled)的新Promise对象。这个过程叫做异常穿透

// 异常穿透:当Promise对象的catch方法没有返回值时(undefined),会默认返回一个新的状态(fulfilled)为成功的Promise对象
Promise.reject('err')
  .then((data) => { // 不执行
    console.log(1, data)
  })
  .catch((err) => {
    console.log(2, err)
  })
  .then((data) => {
    console.log(3, data)
  })
  .then((data) => {
    console.log(4, data)
  })
  .catch((err) => { // 不执行
    console.log(5, err)
  })
/* 
    2 err
    3 undefined
    4 undefined
  */

中断Promise链

有两种中断方式:

  • 抛出一个异常
  • 返回一个reject状态的promise
// 中断Promise链两种方式:1.抛出一个异常  2.返回一个reject状态的promise
Promise.resolve('ok')
  .then((data) => {
    console.log(1, data)
    // throw new Error('err') // 中断then直接进入catch
    return Promise.reject('err') // 中断then直接进入catch
  })
  .then((data) => {
    console.log(2, data)
  })
  .then((data) => {
    console.log(3, data)
  })
  .catch((err) => {
    console.log(4, err)
  })
/* 
  1 ok
  4 err
*/

promise执行顺序

  • new Promise构造函数中的代码是同步执行
  • then方法中回调函数的异步执行的
  • 代码分为同步和异步
  • 异步代码又分为:宏任务和微任务
    • 宏任务:setTimeout、setInterval、ajax、script代码块...
    • 微任务:then的回调...

代码执行顺序:可以简单理解为,在script代码块中,先同后异,先微后宏。

// new Promise 构造函数中的代码是同步执行的
// then中回调 函数是异步执行的
// 先同后异,先微后宏
console.log(1) // 同步
setTimeout(() => {
  console.log(2) // 异步-宏任务
}, 0)
const promise = new Promise((resolve, reject) => {
  // 这里的代码是同步的
  console.log(4) // 同步
  resolve(3)
})
promise.then((data) => {
  console.log(data) // 异步-微任务
})
console.log(5) // 同步
/* 
    1,4,5,3,2
*/

Promise基本原理总结

Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行

Promise 会有三种状态

  • Pending 等待
  • Fulfilled 完成
  • Rejected 失败

状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改

Promise 中使用 resolve 和 reject 两个函数来更改状态

then 方法内部做但事情就是状态判断

  • 如果状态是成功,调用成功回调函数(onFulfilled)
  • 如果状态是失败,调用失败回调函数(onRejected)

留个手写promise的坑 (待完成)

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class Promise1 {
  constructor(executor) {
    // 默认初始状态
    this.status = PENDING
    // 初始化成功状态保存的值
    this.value = undefined
    // 初始化失败状态保存的值
    this.reason = undefined
     // 立即执行,将 resolve 和 reject 函数传给使用者
    executor(this.resolve.bind(this), this.reject.bind(this))
    }
  // 定义一个resolve方法,执行了就是成功状态
  resolve(value) {
    // 只有当pending状态时才会改变状态到fulfilled
    if (this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
    }
  }
  // 定义过reject方法,执行了就是失败状态
  reject(reason) {
    // 只有当pending状态时才会改变状态到rejected
    if (this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
    }
  }
  // 每一个promise都有一个then方法,接收两个参数,成功的回调onFulfilled,失败的回调onRejected
  then(onFulfilled, onRejected) {}
}