一文搞定promise

138 阅读15分钟

初识promise

众所周知promsie是处理异步操作的一种机制,,它可以使异步代码更加优雅和可读。Promise可以表示一个异步操作的最终完成或失败,并返回相应的结果或错误信息。[其具体操作规范可阅读此文](Promises/A+ (promisesaplus.com))

学习Promise时我们要关注它的几个参数一个是Promise的值,一个是Promise的状态

一个Promise对象有三个状态:

  • Pending(等待态):初始状态,表示异步操作还未完成或失败。
  • Fulfilled(成功态):表示异步操作已经成功完成。
  • Rejected(失败态):表示异步操作发生了错误或失败。

Promise对象具有以下特点:

  1. Promise通过构造函数创建:new Promise(executor)executor是一个带有resolvereject参数的函数,在创建Promise时立即执行。
  2. Promise可以被链式调用:通过then()方法将一个或多个回调函数绑定到Promise对象的成功态或失败态,并返回一个新的Promise对象。
  3. Promise可以通过catch()方法捕获错误:catch()方法用于捕获Promise链中的错误,并返回一个新的Promise对象。
  4. Promise可以通过finally()方法指定无论Promise链的结果如何,都会执行的回调函数。
  5. Promise可以使用Promise.all()Promise.race()等静态方法进行多个Promise的并行或竞争处理。

下面我会一一解释每个方法的作用与我踩过的一些坑

创建一个promsie

promise是一个类我们可以通过new关键字进行创建下面是一个示例,下面创建了一个状态为fulfilled值为'OK'的promsie对象,此处看不懂没关系后面我会解释

const p = new Promise((reoslve,reject) => {
    //需要注意的的是虽然promsie是处理异步操作的一种机制,但是此处的代码时同步执行的,这一点后面会讲解
    resolve('OK')
})

Promise的值和三种状态

promise的三种状态分别为:pending,fulfilled,rejected.其中pending为promise的最初是状态,fuldilled为完成状态,rejected为拒绝状态。

创建promise时你可以传入一个构造函数。这个构造函数有两个参数,这两个参数可以随意取名,我习惯将两个参数分别取为resolve和rejected。

第一个回调函数,它可以把promsie的状态由pending变为fulfilled,第二个回调函数可以把promise的状态由pendind转变为rejected,当使用回调函数改变promsie的状态时,它的值会同步的变为会回调函数传入的参数,这里看着可能有点绕,下面有代码可以直观的看见这一点

虽然Promise的状态可以改变,但是能且只能改变一次并且只能由pending转变为另外两种状态

  • pending => fulfilled (resolve())
  • pending => rejected (rejecte())
//p0的状态为pending值为undefined
const p0 = new Promise((reoslve,reject) => {})

//p1的状态为fulfilled值为'OK'
const p1 = new Promise((reoslve,reject) => {
    resolve('OK')  
})

//p2的状态为rejected值为'ERROR',
const p2 = new Promise((reoslve,reject) => {
    reject('ERROR')  
})

上面说了执行回调函数可以改变promise的状态,那除了回调函数之外还有什么方法可以改变promise的状态呢?其实除了回调函数之外使用throw抛出错误也可以改变promsie的值和状态

//throw会将promsie的状态变为rejected,值变为抛出错误的内容
//p的状态为rejected值为1
const p = new Promise((resoleve, reject) => {
      throw 1
}
)

上面已经说完了改变promise状态的方式,在这再提一嘴promise的状态和值的改变是同步的并且只能改变一次,那么这个只能改变一次又是什么意思呢?

//首先在前文说到了promsie回调函数中的代码是同步执行的也就是说是自上向下依次执行
//所以当代码执行到reoslve('OK')时promsie会变为fulfilled态值为'OK'
//但是代码还会继续执行只是执行到reject('ERROR_1')时不会起任何效果
//综上p0为fulfilled态值为'OK'
const p0= new Promise((resolve, reject) => {
    resolve('OK')
    reject('ERROR')
})


//同理p1为rejected态值为'ERROR'  
const p1= new Promise((resolve, reject) => {
   reject('ERROR')
   resolve('OK')
})

reoslve方法

上面说了使用new关键字创建promsie,其实也可以使用promise的静态方法Promise.resolve创建promise使用resolve创建出的promise对象的值和状态由它的参数决定,下面也使用代码演示各种情况

// 先说结论:参数为promise时返回的promsie和参数是全等的,当不为promise时返回一个成功的promise值为传入的参数注意返回的promsie是一个新的promsie,下面会有示例

//当参数为空时
//p为fulfilled态,值为undefined
const p = Promise.resolve()

//当参数为promise时
//首先准备两个promsie
const p0= new Promise((resolve, reject) => {
    resolve('OK')
})
const p1= new Promise((resolve, reject) => {
    reject('ERROR')
})
//p2为fulfilled态值为'OK',p2为rejecte态值为'ERROR'
const p2 = Promise.resolve(p0)
const p3 = Promise.resolve(p1)
p2 === p0  //true
p3 === p1  //true

//当参数不为promsie时
//p4为fulfilled态值为'OK',p5为fulfilled态值为'ERROR'
cosnt p4 = Promise.resolve('OK')
cosnt p5 = Promise.resolve('ERROR')
p4 === p0  //false
p5 === p2  //false

//注意参数可以为任意表达式,只要参数不是一个rejected态的promise返回的promise都会是一个fulfilled态的promsie
////p6为fulfilled态值为Error对象的值
const p6 = Promsie.reoslve(new Error)

reject方法

Promise.reject是promise的静态方法也可以创建promise。与resolve不同的是使用reject创建promise无论参数是什么所创建的promise都是rejected态,值为其参数,下面也使用代码演示各种情况

//当参数为空时
//p为rejected态,值为undefined
const p = Promise.resolve()

//当参数为promise时
//首先准备两个promsie
const p0= new Promise((resolve, reject) => {
    resolve('OK')
})
const p1= new Promise((resolve, reject) => {
    reject('ERROR')
})
//p2为rejecte态值为一个promise,该promise与p0的状态和值完全一致
//p3为rejecte态值为一个promise,该promise与p1的状态和值完全一致
const p2 = Promise.reject(p0)
const p3 = Promise.reject(p1)
p2 === p0  //false
p3 === p1  //false

//当参数不为promsie时
//p4为rejected态值为'OK',p5为rejected态值为'ERROR'
cosnt p4 = Promise.reject('OK')
cosnt p5 = Promise.reject('ERROR')
p4 === p0  //false
p5 === p2  //false

//注意参数可以为任意表达式,返回的promise都会是一个rejected态的promsie
////p6为rejected态值为Error对象的值
const p6 = Promsie.reject(new Error)

上面说了promise的两个静态方法在这做个简单的总结,这两个方法都会返回一个promise,reoslve返回的promsie值和对象由参数决定,reject返回的promsie一定时rejetes态,值为参数。值得注意的是,reoslve方法当参数为promise时它返回的promise和参数是全等的,后面还会由其他返回值也是promise的方法,但是除了这种情况之外他们所有返回的promsie都是一个新的promise

then方法

then方法接收两个回调函数作为参数,第一个参数为promise成功时的回调,第二个为promsie失败时的回调。它的返回值为一个promsie对象这个promise对象的状态和值由回调函数决定。

  1. 当then里面没有回调时此时返回的promise是一个fulfilled态值为undefined
  2. 当有回调但回调无返回值时,这种情况和无回调是一样的
  3. 有回调并且返回值不为promise,会返回一个fulfilled态值为该返回值的promise对象
  4. 有回调并且返回值为promise,会返回一个状态和值与返回的promsie一致的promise对象
  5. 当回调函数出现throw抛出错误,会返回一个rejected态值为抛出错误的值的promise对象

下面会使用代码让你更加直观的认识,下面内容会有点长。

//下面是回调函数该如何执行的说明
const p= new Promise((resolve, reject) => {
    resolve('OK')
})
//由于p时fulfilled态所以会执行then的第一个回调,第二个回调并不会执行,所以第二个参数可以不写
//这里回调函数的参数的值就是调用then方法那个promise的值,在这里就是'OK'
//所以这个控制台就会打印:
//                     这是第一个回调
//                     'OK'
p.then((value) => {
    consloe.log('这是第一个回调')
    console.log(value)   
}, (reason) => {
    consloe.log('这是第二个回调')
    console.log(reason)
})
//下面这就是上面代码的简写形式
p.then((value) => {
    console.log(value)   
})

//当prosmise为rejected态时会执行第二个参数的回调
const p0= new Promise((resolve, reject) => {
    resolve('ERROR')
})
//由于p0时rejected态所以会执行then的第二个回调,第一个回调并不会执行,所以第一个参数可以不写
//这里回调函数的参数的值就是调用then方法那个promise的值,在这里就是'ERROR'
//所以这个控制台就会打印:
//                     这是第二个回调
//                     'ERROR'
p0.then((value) => {
    consloe.log('这是第一个回调')
    console.log(value)   
}, (reason) => {
    consloe.log('这是第二个回调')
    console.log(reason)
})
//下面这就是上面代码的简写形式
p.then(null, (reason) => {
    consloe.log('这是第二个回调')
    console.log(reason)
})

//说完了回调的执行下说说返回的promise
//当then里面没有回调时
//此时返回的promise是一个fulfilled态值为undefined
const p1 = Promise.reoslve().then()

//当有回调但回调无返回值时,这种情况和无回调是一样的
const p2 = Promise.reoslve().then(() => {})

//有回调并且返回值不为promise
//会返回一个fulfilled态值为该返回值的promise对象
//p3是一个fulfilled态值为1的promise对象
const p3 = Promise.reoslve().then(() => {
    return 1
})

//有回调并且返回值为promise
//会返回一个状态和值与返回的promsie一致的promise对象
//需要注意的是这里和resolve方法很像但是他们虽然内容是一样的但是他们并不全等
//p4是一个fulfilled态值为'OK'的promise对象
const p4 = Promise.reoslve().then(() => {
    return new Promise(p)
})
p4 === p  //false  虽然p4和p的内容是一样的但是他们的地址不同(这里牵扯到js的对数据存储方面的知识)

//当回调中出现throw抛出错误
//会返回一个rejected态值为抛出错误的值的promise对象
//p5是一个rejected态值为'ERROR'的promise对象
const p4 = Promise.reoslve().then(() => {
    throw 'ERROR'
    return new Promise(p)
})

//值得一提的是当调用then的promise对象为rejected态而then只有第一个回调参数那么那会返回什么呢
//如下所示
const p0= new Promise((resolve, reject) => {
    reject('ERROR')
})
const p = p0.then((value) => {
    console.log(value)
    return 1
})
//此时p为一个fulfilled态值为undefined的promise对象
//因为他没有第二个回单函数他所以不会进行任何处理此时上述代码可以写成如下
const p0= new Promise((resolve, reject) => {
    reject('ERROR')
})
const p = p0.then((value) => {})
//上面两段代码是完全一致的

catch方法

catch 是 Promise 实例的一个方法,用于捕获 Promise 中发生的错误(即被拒绝的情况)。它接受一个回调函数作为参数,用于处理错误情况,并返回一个新的 Promise 对象。它的返回值为一个promsie对象这个promise对象的状态和值和then的情况完全一致,在这里也简单写一个示例

const p = new Promise((resolve, reject) => {
    reject('ERROR')
})
const p0 = p.catch((reason) => {
    console.log(reason)
    return 1
})

//上述代码会打印出'ERROR',p0是一个fulfilled态值为1的promise

finally方法

finally 是 Promise 实例的一个方法,用于在 Promise 解决(resolve)或拒绝(reject)后,无论结果如何都执行某些操作。它提供了一种处理 Promise 结束时需要执行清理操作的方式。

finally 方法接受一个回调函数作为参数,并返回一个新的 Promise,该 Promise 将与之前的 Promise 状态相同。当原始的 Promise 被解决或拒绝时,无论如何,传入的回调函数都会被执行。但是当其回调函数返回值为一个pending或rejected状态的promsie或者在其回调中使用throw抛出了错误时返回的promsie就不会再继承其父级promsie,下面是代码示例

//下面p0-p5全部都是fulfilled态值为'OK',下列情况并不受finally回调函数返回值的影响
const p= new Promise((resolve, reject) => {
    resolve('OK')
})
const p0 = p.finally(() => undefined) 
const p1 = p.finally(() => {}) 
const p2 = p.finally(() => Promise.resolve()) 
const p3 = p.finally(() => '123') 
const p4 = p.finally(() => Promise.resolve('123')) 
const p5 = p.finally(() => Error('123')) 

//当返回值为pending的promise
//p6为pending态值为undefined
const p6 = p.finally(() => new Promise(() => {}))

//当返回值为rejected的promise
//p7为rejected态值为1234
const p7 = p.finally(() => new Promise((resolve, rejected) => rejected(1234)))

//在回调函数中使用throw抛出错误
//p8为rejected态值为123
const p8 = p.finally(() => new Promise((resolve, rejected) => {
    throw 123
}))

all方法

Promise.all 是一个 Promise 的静态方法,它接收一个由 Promise 对象组成的可迭代对象(比如数组),并返回一个新的 Promise。该 Promise 在传入的所有 Promise 都成功解决后才会被解决,并将解决结果按照原始顺序组成一个数组返回;如果任何一个 Promise 失败(rejected),则该 Promise 会立即被拒绝,并返回拒绝原因。

//创建三个promise对象p0和p为fulfilled态,p2为rejected态
const p0= new Promise((resolve, reject) => {
    resolve('1')
})
const p1= new Promise((resolve, reject) => {
    resolve('2')
})
const p2= new Promise((resolve, reject) => {
    reject('3')
})
//p3为fulfilled态值为[1,2]
//p4为rejected态值为3
const p3 = Promise.all([p0,p1])
const p4 = Promise.all([p0,p1,p2])

race方法

Promise.race 是一个静态方法,用于创建一个新的 Promise,该 Promise 将与传入的多个 Promise 对象竞争(race),并采用第一个解决或拒绝的 Promise 的结果。

//创建三个promise对象p0和p为fulfilled态,p2为rejected态
const p0= new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('1')
    },0)
})
const p1= new Promise((resolve, reject) => {
    resolve('2')
})
const p2= new Promise((resolve, reject) => {
    reject('3')
})
//p3为fulfilled态值为2
const p3 = Promise.race([p0,p1,p2])

使用Promise

通过上面的学习我们学会了promise相关的种方法的使用方法和作用现在我来分享一下我在学习promise种遇到的那些坑

几个Promise执行顺序的问题

都知道promise是用来处理异步操作的那使用promise时那些代码是异步的的哪些又是同步的呢? 这个问题其实我在上文有提到过,现在我来详细解释一下这个问题

//执行下列代码控制台会依次打印出1 2 3 4 5 6 7
const p = new Promise((resolve, reject) => {
    //这个回调函数的代码全是同步执行的
    console.log(1)
    resolve(5)
    console.log(2)
    setTimeout(() => {
        console.log(7)
    },0)
})
console.log(3)
//异步执行的代码在then或者catch的会回调函数中
p.then((value) => {
    //这里的代码是异步执行的
    //如果学过微任务和宏任务你就知道这里的代码会被放在微任务队列中实现
    console.log(value)
    cons.log(6)
})
console.log(4)

这里对上述代码的执行顺序进行讲解,首先代码会自上向下依次执行:

  1. console.log(1)会在控制台打印1
  2. reoslve(5)会将promise状态变为fulfilled值变为5
  3. console.log(2)会在控制台打印2
  4. setTimeout会将回调函数推入宏任务队列中执行也就是 console.log(7)
  5. console.log(3)会在控制台打印3
  6. p.then()当p的状态为fulfilled时将第一个回调推入微任务队列中执行,在这也就是打印value和6
  7. console.log(4)会在控制台打印3
  8. 当所有的同步任务执行完执行微任务队列代码打印value也就是5
  9. 打印完value之后打印6
  10. 微任务队列执行完会执行宏任务队列中的的console.log(7)

所以上述代码最后打印的结果是1 2 3 4 5 6 7

下面再来一个例子

//下面代码会打印1 2 3 4 
const p = new Promise((resolve, reject) => {
   resolve()
})

//此句代码在下面用p.then()表示
p.then(() => {
    console.log(1)
//此句代码下面使用p.then().then()表示
}).then(() => {
    console.log(3)
})


//此句代码在下面用p.then()表示
p.then(() => {
    console.log(2)
//此句代码下面使用p.then().then()表示
}).then(() => {
    console.log(4)
})        

代码执行顺序为:

  1. reolve() 将p的状态变为fulfilled值变为undefined
  2. p.then() 由于p为fulfulled态所以会将第一个回调函数推入微任务队列,并返回一个promise对象此时他为pending,此时微任务队列有一个console.log(1)任务
  3. p.then().then()由于上个promsie还是pendong态所以不执行
  4. p.then() p的为fulfulled态会将第一个回调函数推入微任务队列,并返回一个promise对象此时他为pending,此时微任务队列有一个console.log(1)任务和一个此时微任务队列有一个console.log(2)任务
  5. p.then().then()由于上个promsie还是pendong态所以不执行
  6. 同步代码执行完执行微任务队列中的任务console.log(1),此时then的回调函数执行完成他所返回的promise的状态会变为fulfilled,当他返回的promise的状态变为fulfilled时p.then().then()的·回调会执行会把console.log(3)推入微任务队列此时微任务队列有一个console.log(2)任务和一个此时微任务队列有一个console.log(3)任务
  7. 同第六步一样会将console.log(4)推入微任务队列并且打印2
  8. 打印3
  9. 打印4

综上代码会打印1 2 3 4

好了到这文章就结束了,在这作者再给出几点自己对学习使用promise时的心得:

  1. then的回调函数它的执行一定是与调用此方法的promise决定的,必须不为pending态时他的回调才会执行就像上面的第二个例子,只有调用此方法的promise状态变为fulfilled或者rejecteed时才会执行并且它的回调是在微任务队列中执行的
  2. promise的每个方法都会返回一个promise但是只有reoslve方法返回的promsie会与输入的promise全等,这点上面有提到的,其他方法就算返回的promsie的值和状态与输入的promsie完全一样但是他们依然不全等,因为他们存储的地址同

希望这篇文章对你有所帮助,如果还有什么不懂的或者文章存在什么问题欢迎在评论区讨论。我会尽力为大家解答或者改正自己的错误,如果大家需要的话后面还会更新async和await相关的知识