嘿,兄弟,快来学Promise。

111 阅读11分钟
Promise
案例引导

调用一个函数,这个函数中发送网络请求(定时器模拟),如果网络请求成功,告知调用者成功,并返回相应的数据,如果请求失败,告知调用者请求失败并返回错误信息

function requestData(url, successCallback, errCallback) {
​
  setTimeout(() => {
    if (url === 'coderwhy') {
      const data = '我是返回回来的数据'
      successCallback(data)
    } else {
      let err = '我是请求发生错误的错误信息'
      errCallback(err)
    }
  }, 1000)
}
​
requestData('coderwhy', (res) => {
  console.log(res);
}, (err) => {
  console.log(err);
})

这样一种异步回调的处理方式有什么缺点呢?

第一,我们必须自己去命名成功和失败的回调函数名称,每个人都不一样。

第二,这样的方式不利于维护,如果有多个网络请求之间相互依赖,那么就会造成回调地狱。

那么有没有一种统一的回调方式去处理这种情况呢,比如不管怎么样,requestData函数都return一个东西,这个东西里面包含了不管是成

功还是失败所需要的信息,我们只要调用这个东西对应的方法就可以拿到我们想要的信息,这种方式就是Promise。

Promise是期约的意思,意思是承诺不管什么情况,我都会给你一个结果。

什么是promise
  • Promise是异步编程的一种解决方案,在ES6之前,我们执行异步代码往往需要得到一个回应,这个时候我们会定义回调函数,
  • 但是因为这种回调的形式没有一个统一的形式
  • 而promise的出现统一了回调的这种形式,并且Promise能够很好的解决毁掉地狱的问题
  • 首先Promise是一个类,使用的时候通过new关键字创建Promise的实例,创建实例的时候需要传入一个执行函数(executor)。
  • 这个执行函数会在实例创建的时候立即执行,并且异步代码写在执行函数内。另外executor也需要两个参数函数,resolve和reject
  • resolve方法会在异步代码成功的时候调用,catch方法会在异步代码失败的时候调用。
  • 当调用resolve方法时,会执行Promise实例的then方法传入的回调
  • 当调用reject方法时,会执行Promise实例的catch方法传入的回调
用Promise重构案例
function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'coderwhy') {
        const res = '我是请求回来的数据'
        resolve(res)
      } else {
        const err = 'err message'
        reject(err)
      }
    }, 1000)
  })
}
​
requestData('coderwhy')
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.log(err);
  })
​
Promise的三种状态

使用Promise时,会有三种状态,分别是pending(悬而未决),fulfilled(已敲定),rejected(已拒绝)。

  • 当执行executor时(没有调用resolve和reject函数),处于pending状态
  • 当执行resolve函数时,处于fulfilled状态
  • 当执行reject函数时,处于rejected状态
  • 状态一经决定就不能修改,也就是说在调用resolve函数之后,处于fulfilled状态,之后再调用reject函数,也不会处于rejected状态,反过来也一样
new Promise((resolve, reject) => {
  console.log('pending状态'); //pendding状态
​
  resolve('我是结果') // fulfilled状态
​
  reject('我是错误信息') // rejected状态
})
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.log(err);
  })
resolve函数的参数

我们调用resolve函数的时候,会给函数传入一些参数,有以下几种情况:

  • 当传入的参数是普通值或者普通对象时,promise的状态由自己决定
new Promise((resolve, reject) => {
  resolve('成功')
})
  .then(res => {
    console.log('1:', res);
  })
  .catch(err => {
    console.log('1:', err);
  })
  • 当传入的参数是另一个Promise的时候,那么promise的状态由传入的promise的状态决定
const newPromise = new Promise((resolve, reject) => {
  // resolve('成功')
  reject('失败')
})
​
new Promise((resolve, reject) => {
  resolve(newPromise)
})
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.log(err);
  })
  • 当传入的参数是一个thenable对象时(实现了then方法),会执行该对象的then方法,并根据then方法的resolve和reject来决定该promise的状态
new Promise((resolve, reject) => {
  const thenObj = {
    then(resolve, reject) {
      resolve('成功')
    }
  }
  resolve(thenObj)
})
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.log(err);
  })
实例方法-then方法

then方法是Promise实例对象的方法,放在Promise.prototype原型对象上

  • 接收两个参数

then方法其实可以接收两个参数,一个是fulfilled的回调,一个是rejected的回调

//之前我们写fullfiled的回调和rejected的回调是分开写在then和catch里面
new Promise((resolve, reject) => {
  resolve('成功')
}).then(res => {
  console.log(res);
})
  .catch(err => {
    console.log(err);
  })
​
//其实也可以统一写在then里面:
new Promise((resolve, reject) => {
  resolve('成功')
}).then(res => {
  console.log(res);
}, err => {
  console.log(err);
})
  • 多次调用

Promise实例的then方法可以多次调用,执行结果都一样

const promise = new Promise((resolve, reject) => {
  resolve('成功')
})
//第一次调用
promise.then(res => {
  console.log(res);
}, err => {
  console.log(err);
})
//第二次调用
promise.then(res => {
  console.log(res);
}, err => {
  console.log(err);
})
  • then方法第一个回调函数的返回值:

不管该回调函数返回什么值,内部都会重新创建一个新的promise,然后将返回的值作为resolve函数的参数。

情况一:返回值为普通值

then方法的第一个回调函数也可以有返回值,当返回了一个普通值的时候,会把这个普通值包裹在一个新的Promise实例上,并调用该实例的resolve方法,把该值作为参数

const promise = new Promise((resolve, reject) => {
  resolve('成功')
})
​
promise.then(() => {
  return 'aaaa'  //这里内部会 new Promise 然后把aaaa作为该实例的resolve函数的参数传递,
}).then(res => {  //所以这里then方法实际上是上面的新Promise实例的resolve函数执行的回调,
  console.log(res); // aaa
})

情况二:返回值为一个Promise实例

即使当then的回调函数返回一个Promise的实例,内部也还是会创建一个新Promise的实例,然后把Promise的实例作为新Promise的resolve的参数传递

那这就回到了我们之前讲过的,如果一个Promise的resolve函数的参数为Promise实例,那么这个Promise的状态将由这个参数Promise来决定。

const promise = new Promise((resolve, reject) => {
  resolve('成功')
})
​
promise.then(() => {
  return new Promise((resolve, reject) => {//这里直接返回了一个promise,但是其实内部还是会重新创建一个promise实例,然后把返回的promise作为新创建的promise的resolve函数的参数传递
    resolve('成功哒哒哒')
  })
}).then(res => { //所以这里then方法实际上是上面的新Promise实例的resolve函数执行的时候的回调
  console.log(res); //哒哒哒
})

情况三:返回一个thenable对象

同样的道理,如果返回一个thenable对象,那么会将该thenable对象作为新promise实例的resolve方法的参数传递。而之前也说过,resolve方法的参数如果是一个thenable对象

那么会调用该thenable对象的then方法。

const promise = new Promise((resolve, reject) => {
  resolve('成功')
})
promise.then(() => {
  return {
    then(resolve, reject) {
      reject('错误')
    }
  }
}).then(undefined, err => {
  console.log(err);  //错误
})
实例方法-catch方法

catch方法也是Promise实例对象上的一个方法,也是放在Promise的原型上的 Promise.prototype上

  • 多次调用

和then方法一样,catch方法也可以多次调用,多次调用结果一样

const promise = new Promise((resolve, reject) => {
  reject('err message')
​
})
​
promise.catch(err => {
  console.log(err);
})
promise.catch(err => {
  console.log(err);
})
promise.catch(err => {
  console.log(err);
})
​
  • 抛出异常

当在executor中调用reject函数时会执行catch方法,exectuor内代码发生错误也会执行catch函数

const promise = new Promise((resolve, reject) => {
  throw new Error('err message')
})
​
promise.catch(err => {
  console.log(err);
})
  • catch的写法

一起看看catch有几种写法:

const promise = new Promise((resolve, reject) => {
  reject('err message')
})
//第一种:
promise.catch(err => {
  console.log(err);
})
//第二种:
promise.then(undefined, err => {
  console.log(err);
})
//第三种:
promise.then(res => {
  return 111
}).catch(err => {
  console.log(err); // err message
})

仔细研究一下第三种写法,这里打印的err必然是promise调用reject函数传递进来的 ‘err message’,但是如果then方法的第一个回调里面也返回了promise

并且也调用了reject方法,那这个时候的catch打印的会是谁的错误信息呢?

const promise = new Promise((resolve, reject) => {
  reject('err message')
})
promise.then(res => {
  return new Promise((resolve, reject) => {
    reject('inner err message')
  })
}).catch(err => {
  console.log(err); // err message
})

可以看到打印的依然是err message

那么当第一个promise执行的是resolve函数,但是then方法的第一个参数返回promise并且调用了reject函数呢?

const promise = new Promise((resolve, reject) => {
  resolve()
})
promise.then(res => {
  return new Promise((resolve, reject) => {
    reject('inner err message')
  })
}).catch(err => {
  console.log(err);  // inner err message
})

可以看到,打印的是inner err message,也就是得到一个结论:当使用.then().catch()的形式捕获异常的时候,catch方法会优先捕获外部promise的异常,

如果外部没有异常,而内部有异常的时候,那么会捕获内部promise的异常

细节题:

const promise = new Promise((resolve, reject) => {
  reject('err message')
})
​
promise.then(res => {  
  console.log(res);
})
​
promise.catch(err => {
  console.log(err);
})
​
执行结果:
err message
------------
Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
​
//这段代码会报错,当然报错和promise.catch方法无关,而是和promise.then方法有关,这是因为promise的executor内部调用了reject函数
//而then方法内部却没有定义对应的回调函数来处理reject()函数的错误信息,所以会报错。
//解决报错的方法就是在then方法内部添加上reject函数的回调:
promise.then(res => {
  console.log(res);
}, err => {
  console.log(err);
})
​
  • catch有返回值 then方法里面有返回值,会包裹promise,那catch方法有返回值,同样会包裹promise,并且会调用新promise的resolve方法传递return的值

除非return的值为报错或者抛出异常,才会调用reject方法,否则调用resolve

const promise = new Promise((resolve, reject) => {
  reject('err message')
})
​
promise.then(res => {
  console.log(res);
}).catch(err => {
  return err  //会创建新promise,并用其resolve方法传递err,
}).then(res => {
  console.log('res:',res);  // 'res:' err message 
}).catch(err => {
  console.log('err:',err);  
})
实例方法-finally方法

finally是不管promise处于fulfilled状态还是rejected状态都会执行的方法,通常用来做一些清除工作,finally的回调函数不接收参数。

const promise = new Promise((resolve, reject) => {
  // resolve('success')
  reject('err happend ')
})
​
promise.then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);  
}).finally(() => {
  console.log('finally happend');
})
类方法-resolve和reject方法
  • Promise.resolve()

如果我们想把一个对象转换成promise,可能会这样:

const obj = { name: 'why' }
const promise = new Promise((resolve, reject) => {
  resolve(obj)
})

但是这样写有点麻烦,我们可以直接使用Promise.resolve()方法,将一个值或对象直接变成promise,上面的代码等同于:

 const promise1 = Promise.resolve(obj)
promise1.then(res => {
  console.log(res);
})
​
// 同样,如果Promise.resolve方法的参数是一个promise实例或者是一个thenable对象,那么会依照我们之前讲的三种情况
const promise2 = Promise.resolve(new Promise((resolve, reject) => {
  reject('err message')
}))
promise2.then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})
  • Promise.reject()

Promise.reject和Promise.catch的效果一样,不同的是,Promise.catch不会有三种情况,不管传入的是什么,都相当于调用reject方法。

const promise = Promise.reject('err message')
promise.then(undefined, err => {
  console.log(err);  //err message
})
类方法-all和allSettled
  • all方法

现在有这种情况:有三个promise,假如我们想让这三个promise的状态都处于fulfilled状态时,拿到它们的结果,就可以使用Promise.all方法

const p1 = new Promise((resolve, reject) => {
 setTimeout(() => {
  resolve('success1')
 }, 1000)
})
​
const p2 = new Promise((resolve, reject) => {
 setTimeout(() => {
  resolve('success2')
 }, 2000)
})
​
const p3 = new Promise((resolve, reject) => {
 setTimeout(() => {
  resolve('success3')
 }, 3000)
})
​
Promise.all([p1, p2, p3, 'abc']).then(res => {
 console.log(res); //[ 'success1', 'success2', 'success3', 'abc' ]
​
})
1.all方法接收一个数组参数,里面放想要fulfilled的promise,如果数组中有普通值,那么会调用Promise.resolve()将其转换成promise
2.all方法的返回值也是一个promise,可以通过then方法拿到返回的结果,结果也是一个数组,里面包含了每个promise的resolve结果
3.并且结果的顺序和p1p2p3的顺序有关
4.假如中间有一个promise处于rejected状态,那么Promise.all就会变成rejected状态
  • allSettled方法

all方法有一个缺陷,当任何一个promise返回rejected,那么all会立即变成rejected,那么其他处于fullfilled状态的promsie结果我们获取不到

如果我们想让三个promise的状态不管是fulfilled还是rejected状态,都把它们的状态返回,就可以使用Promise.allSettled

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success1')
  }, 1000)
})
​
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err')
  }, 2000)
})
​
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success3')
  }, 3000)
})
​
Promise.allSettled([p1, p2, p3, 'abc']).then(res => {
  console.log(res);
})
//结果:
[
  { status: 'fulfilled', value: 'success1' },
  { status: 'rejected', reason: 'err' },
  { status: 'fulfilled', value: 'success1' },
  { status: 'fulfilled', value: 'abc' }
]
​
1.allSettled同样也返回一个promise,这个promise始终是fulfilled状态,并且通过then拿到结果,结果是一个数组,
2.数组中是一个个对象,里面表示了每一个promise的状态,以及resolve或reject状态的结果
类方法-race和any
  • race方法

假如我们想让多个promise中,只要有其中一个promise先返回结果,就立即拿到这个结果。就可以使用Promise.race()

如果先返回结果的这个promise的状态为rejected,那么race的状态也变成rejected

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success1')
  }, 1000)
})
​
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err')
  }, 2000)
})
​
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success1')
  }, 3000)
})
​
Promise.race([p1, p2, p3]).then(res => {
  console.log(res);  //success
}).catch(err => {
  console.log(err);
})
  • any方法

any是ES2022新增的一个方法,与race不同的是,如果先返回的promise是rejected状态,那么any也会等到有一个promise的结果返回fulfilled状态的时候,

把这个fulfilled状态的promsie的状态返回,除非所有的promise都返回rejected状态,那么race会返回rejected状态

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success1')
  }, 1000)
})
​
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err')
  }, 2000)
})
​
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success1')
  }, 3000)
})
​
Promise.any([p1, p2, p3]).then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})

全都返回rejected状态:

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err1')
  }, 1000)
})
​
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err2')
  }, 2000)
})
​
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('err3')
  }, 3000)
})
​
Promise.any([p1, p2, p3]).then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);  // All promises were rejected
})