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变为fulfilledreject:失败的回调函数,将状态从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.resolve和 Promise.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) {}
}