JavaScript中的Promise
1. Promise 的介绍
Promise 是异步编程的一种新的解决方案,从早期的回调函数、事件相比,更加合理和强大。语法上来说,Promise 是一个对象,可以获取异步操作成功或是失败的结果。
Promise 有三种状态:pending (进行中),fulfilled (resolved 已成功),rejecred (已失败)。只有当异步操作的结果可以决定当前是哪一种状态,任意其他操作都无法改变这个状态。
一旦状态发生改变,就不会再变,任意时刻都可以获取到这个结果。Promise 对象的状态改变只有两种情况:从pending (进行中)改变为resolved (已成功),从pending (进行中)改变为rejected (已失败)。
1.1 Promise 的优缺点
而通过 Promise 对象就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
当然,Promise 也有一些缺点,例如:
- 无法取消 Promise,一旦新建就会立即执行,无法中途取消
- 如果不设置回调函数。Promise抛出的错误不会反应到外界
- 当处于 pending状态时,无法得知目前进展到哪一个阶段 ( 是刚刚开始执行还是即将完成操作 )
2. Promise 的基本用法
2.1 生成实例
ES6规定,Promise 对象是一个构造函数,用来生成 Promise 实例。
// 这就简单的创建了一个 Promise 实例
new Promise((resolve, reject) => {
  if(/* 异步操作是否成功 */) {
    resolve(value)
  }else {
    reject(error)
  }
})
上面的代码中,可以看到 Promise 构造函数接受了一个函数作为参数,这个函数有携带两个参数: resolve、reject。这两个参数都是函数,由 JavaScript 引擎提供,不需要自己传参。
- resolve:将- Promise对象的状态改变为:- resolved(已成功),可以在异步操作完成后进行执行,并且可以将异步操作的结果作为参数传递出去。
- reject:将- Promise对象的状态改变为:- rejected(已失败),同样在异步操作完成后进行执行,同样可以将异步操作的结果作为参数传递出去。
2.2 使用 then() 指定回调函数
在 Promise 实例生成后,可以使用 then() 方法分别指定 resolved 状态和 rejected 状态的回调函数
then() 方法接受两个函数作为参数,其中,第一个作为 resolved 的回调函数,第二个作为 rejected 的回调函数,两个函数都是可选的,不一定非要提供。
new Promise((resolve, reject) => {
  // reject('拒绝状态')
  resolve('成功状态')
})
  .then(
    (value) => {
      console.log('成功的业务处理')
    },
    (reason) => {
      console.log('拒绝的业务处理')
    }
  )
// 简单的伪装为一个网络请求
let statusCode = 404  // 假设这是请求返回的状态码
// 实例化 Promise 对象
const p = new Promise(function (resolve, reject) {
   // 成功
   if ( statusCode >= 200 && statusCode < 300 ) {
     let data = '数据库中的用户数据'
     resolve(data)
   }
   // 失败
   else if (statusCode === 404) {
     let err = '获取数据失败'
     reject(err)
   }
})
// 调用 promise 对象的 then 方法
p.then(
  function (value) {
    console.log(value)
  },
  function (reason) {
    console.log(reason)
  }
)
2.3 链式调用 then()
Promise.then() 其实也一个 Promise,同样会返回状态。
只是没有设置的情况的默认返回的就是 resolved 状态,且不会携带任何值。
哪怕手动返回了一个 非Promise类型 的值,也会被自动转换为 Promise.resolve() 的参数。
// 创建一个 Promise 实例,修改状态为 reject
let p1 = new Promise((resolve, reject) => {
  reject('拒绝')
})
let p2 = p1
  .then(
    (value) => {
      console.log(value)
    },
    (error) => {
      console.log(error)
      // 如果这里不手动返回一个 Promise 的 reject 状态,下一个 then 就会执行 resolve 状态的回调函数
      // return new Promise((resolve, reject)=>{
      //   reject('失败')
      // })
    }
  )
  .then(
    (value) => {
      console.log('成功')
    },
    (error) => {
      console.log(error)
    }
  )
// 输出结果为:
// 拒绝
// 成功
3. Promise.resolve() 和 Promise.reject()
Promise.resolve() 和 Promise.reject() 可以将一个现有对象转换为 Promise 对象。
Promise.resolve()
Promise.reject()
// 就相当于
new Promise(resolve => resolve())
new Promise((resolve,reject) => reject())
4. Promise.prototype.catch()
在上面的例子中,虽然可以使用 then() 里面的第二个函数参数作为发送错误的回调函数;
但是如果只需要失败的回调,除了可以将第一个参数忽略;还可以使用 catch() 方法,catch() 方法不但可以捕获 Promise 实例抛出的错误,then() 运行时发出的错误同样可以捕获。
// 正常情况下,如果只需要失败的回调,可以将第一个参数设置为 null 或是 undefined 即可
new Promise((resolve, reject) => {
    reject('已失败')
}).then(null, error => {
    console.log(error)
})
// 但是上面代码完全可以使用 catch() 进行优化,效果是一样的,但是增加了可读性
new Promise((resolve, reject) => {
    // reject('已失败')
	throw new Error('请求超时')
}).catch(error => {
    console.log(error)
})
4.1 多个 Promise 对象
Promise 的错误具有 "冒泡" 性质,会一直往后传递,知道被捕获为止。
new Promise(resolve => {
  resolve('ok')
})
  .then((value) => {
    // 手动返回一个 rejected 状态
    return new Promise((resolve, reject) => {
      reject('P2')
    })
  })
  .then((value) => {
    console.log(value)
  })
  // 如果上面的 then 都没有设置第二参数 ( rejected 的回调函数 ), 就会'冒泡'到最后的 catch() 进行执行
  .catch((error) => {
    console.log(error)
	// p2
  })
5. Promise.all()
Promise.all() 方法可以一个数组作为参数,其中数组中的元素都是 Promise 实例,如果不是,会自动执行 Promise.resolve() 转换为一个 Promise 实例,再进一步处理。
Promise.all() 提供了并行执行异步操作的能力,会在所有的异步操作执行完毕且状态全为 resolved 才会将自身状态修改为 resolved, 并且把所有 Promise 实例的返回值打包为一个数组。
只要有一个 Promise 实例的状态变为 rejected, Promise.all() 就会直接变为 rejected,并且接收第一个 rejected 实例的返回值作为参数传递出去。
const promise_1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve('第一个异步请求成功了')
    reject('第一个异步请求失败了')
  }, 1000)
})
const promise_2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('第二个异步请求成功了')
  }, 1000)
})
// 以第一个被 reject 的实例的返回值作为 Promose.all 的回到函数的参数
Promise.all([promise_1, promise_2]).then((value) => {
  console.log(value)
  // 第一个异步请求失败了
  // 如果第一个 Promise 实例为成功则返回:['第一个异步请求成功了', '第二个异步请求成功了']
})
5.1 如果 Promise 实例自己捕获了错误呢?
上文说到,如果捕获到 rejected 状态就会直接返回给 Promise.all() 方法,那么作为参数的 Promise 实例身上就自己定义了 catch() 呢?如果定义了,那么就不会触发 Promise.all() 的 catch() 方法。
因为 Promise.catch() 方法返回的也是一个 Promise, 但由于没有主动定义返回状态,所以返回到 Promise.all() 这里自然就是 resolved 状态了,且参数为空。
const promise_1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('第一个异步请求失败了')
  }, 1000)
}).catch((error) => {
  console.log(error)
  // 第一个异步请求失败了
})
const promise_2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('第二个异步请求')
  }, 1000)
})
Promise.all([promise_1, promise_2]).then((value) => {
  console.log(value)
  // [ undefined, '第二个异步请求']
})
6. Promise.allSettled()
上面学习的 Promise.all() 方法,需要所有实例返回 resolved 状态才算成功,只要由一个状态为 rejected 状态就会直接修改状态为 rejected 状态,无论其他操作是否完成。
为了解决这个问题,在 ES11 中新增了 Promise.allSettled() 方法,用来确定一组 Promise 是否都完成了 ( 无论成功与否 )。
同样的,Promise.allSettled() 也是接收一个 Promise 实例组成的数组作为参数,并且返回一个新的 Promise。
只是不同的是,Promise.allSettled() 方法只有等到所有的 promise 实例的状态都改变时,返回的 Promise 的状态才会改变。
// 虽然有一个作为参数的 Promise 实例状态为 rejected,但依然返回了数据
const p1 = new Promise((resolve, reject) => {
  reject('失败')
})
const p2 = new Promise((resolve, reject) => {
  resolve('成功')
})
Promise.allSettled([p1, p2]).then((value) => {
  console.log(value)
  // [
  //    {status: 'rejected', reason: '失败'},
  //    {status: 'fulfilled', value: '成功'}
  // ]
})
7. Promise.prototype.race()
Promise.race() 同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
Promise.all() 需要等作为参数所有的 Promise 实例执行完成才会改变状态; 而 Promise.race() 是当作为参数的 Promise 实例有一个先改变状态时 ( 无论成功与否 ), 直接返回给 Promise.race() 了。
7.1 网络请求超时的案例
// p1 为一个网络请求
let p1 = new Promise((resolve, reject) => {
  if(/*请求是否成功*/) {
    resolve()
  }else {
    reject('请求失败')
  }
})
// p2 为一个 2s 的定时器
let p2 = new Promise((resolve, reject) => {
  setTimeout(()=>{
    reject('请求超时')
  }, 2000)
})
// 执行 Promise.race() 方法,只要请求时间超过 2s,就会直接返回请求超时的错误
Promise.race([p1, p2])
  .then((value) => {
    console.log(value)
  })
  .catch((error) => {
    console.log(error)
  })
8. Promise.prototype.finally
Promise.finally() 方法是 ES2018 引入的,用于指定不管 Promise 对象最后状态如何,都会执行的操作。
// 封装的 Ajax 请求
function ajax(url) {
  return new Promise((resolve, reject) => {
    // 显示 loading 动画元素
    loading.style.display = 'block'
    let xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.send()
    xhr.onload = function () {
      if (this.status == 200) {
        resolve(JSON.parse(this.response))
      } else {
        reject('加载失败')
      }
    }
    xhr.onerror = function () {
      reject(this)
    }
  })
}
// 请求的 url 地址
let src = 'https://api.apiopen.top/api/sentences'
ajax(src)
  .then((value) => {
   //请求成功打印获取的数据
    console.log(value)
  })
  .finally(() => {
    // 无论成功与否都要隐藏 loading 动画元素
    loading.style.display = 'none'
  })
9. Promise.any()
在 ES2021 中,引入了 Promise.any() 方法,这个方法也是接收一组 Promise 实例作为参数,包装为一个新的 Promise 实例返回。
只要实例中有一个状态改变为 resolved,包装函数就会变为 resolved 状态,如果所有的实例参数都为 rejected 状态,那么包装函数也会变为 rejected 状态。
虽然 Promise.any() 和 Promise.race() 很像,只有一点不同:就是 Promise.any() 不会因为某个 Promise 变为 rejected 状态而结束,必须等到所有参数 promise 都变为 rejected 状态才会结束。
简单来说:
- Promise.any(): 返回的是- 第一个状态改变为 resolved,获取先成功的异步操作
- Promise.race(): 返回的是- 第一个状态改变的 Promise,获取先完成的异步操作
let p1 = Promise.reject('reject A')
let p2 = Promise.resolve('resolve A')
let p3 = Promise.resolve('resolve B')
let p4 = Promise.reject('reject B')
Promise.any([p1, p2, p3, p4]).then((value) => {
  console.log(value)  // resolve A
})
Promise.race([p1, p2, p3, p4])
  .then((value) => {
    console.log(value)
  })
  .catch((error) => {
    console.log(error)  // reject A
  })
10 总结
- Pomise具有三种状态,- pending、- resolved、- rejected
- Promise可以通过- then指定成功和失败的回调函数,并且默认返回一个- Promise.resolve()
- Promise.resolve()和- Promise.reject()其实就是- Promise的简写形式
- Promise.catch()可以作为前面- promise的- rejected状态的回调函数
- Promise.all()可以获取一组- Promise成功的返回值,或者第一个失败的返回值
- Promise.allSettled()可以获取一组- Promise的返回数据
- Promise.race()可以获取一组- Promise中最先完成的异步操作
- Promise.finally()状态改变后就会执行 ( 无论是- resolve还是- reject)
- Promise.any()可以获取一组- Promise中最先成功的异步操作

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号