JavaScript之Promise
写东西真是难啊,一是因为肚子里没多少货,二是因为掌握的东西也太细碎表面。好吧,这也是为什么要写的原因,希望能借此倒逼一下自己。
本来想着一个主题接一个主题的来写,但是发现好难。好吧,那就从自己熟悉的东西开始写。
Promise长啥样
先看大致的代码
new Promise(function (resolve, reject) { // 执行异步操作...... // 判断异步操作的结果 if (/* success */) { resolve(value); } else { reject(err); } }).then(res => { // 成功,执行后续操作 }).catch(err => { // 失败,处理错误 })
Promise的字面意思就是承诺,承诺表示表示将要做但是还未做的事情,而且承诺还伴随着这件事情是成功或者失败的反馈。所以当new一个Promise的时候,就相当于告诉js引擎去帮我们执行一些操作(同步或者异步的都可以),执行完后再告诉我们结果,然后我们再根据结果的成功与否去做之后的操作。
举个栗子
console.log('start...') new Promise(function (resolve, reject) { console.log('start promise...') // 大约0.5秒后执行 setTimeout(() => { console.log('start timeout...') // 随机生成一个num,1 <= num <= 10 const num = parseInt(Math.random() * 10) + 1 // 大于5算成功,否则算失败 if (num > 5) { resolve('success') } else { reject('failed') } console.log('end timeout...') }, 500) console.log('end promise...') }).then(res => { console.log(res) }).catch(err => { console.log(err) }) console.log('end...')
运行结果:
start...
start promise...
end promise...
end...
start timeout...
end timeout...
failed/success
这段代码,首先会打印出start...字符串,然后当我们new Promise()的时候,引擎会立刻执行Promise里的代码,所以打印出start promise...。紧接着,遇到setTimeout,所以直接跳到end priomise...处打印该字符串。这个时候,Promise里的代码暂时执行完毕,引擎将切换到Promise外部执行后续的同步代码,打印出end...。大约在0.5秒后,引擎将切回Primise内部执行setTimeout的回调函数,于是打印出start timeout...,但是不管resolve还是reject,只要没有return语句,都将打印出end promise...,最后才是执行then或者catch里的回调函数。
Promise的3种状态
Promise对象一共有pedding(进行中),fulfilled(已成功)和rejected(已失败)3种状态。从创建Promise到resolve函数执行之前,都是pedding状态,resolve()执行后变成fulfilled状态,reject()执行后变成rejected状态。
Promise的then方法
Promise的then方法是在Promise的状态改变时调用,接收两个参数,第一个参数是成功时的回调函数,第二个参数(可选)是失败时的回调函数。
then方法会返回一个新的Promise实例,于是我们可以继续在then后面写then(catch方法同样返回Promise实例,可以链式调用)。这个特性挺有用的,下面用示例说一下。
首先,先创建一个函数,该函数返回一个Promise实例,该Promise成功或失败的概率各50%。
const isMoreThanFive = function () { return new Promise(function (resolve, reject) { setTimeout(() => { const num = parseInt(Math.random() * 10) + 1 if (num > 5) { resolve('success') } else { reject('failed') } }, 500) }) }
一个then函数的情况:
isMoreThanFive() .then((res) => { console.log(res) }) .catch(err => { console.log(err) }) // 0.5秒后输出success或者failed
两个then函数的情况:
isMoreThanFive() .then((res) => { console.log('first ' + res); return isMoreThanFive() }) .then(res => { console.log('second ' + res); }) .catch(err => { console.log(err) })
这种情况,如果第一个isMoreThanFive执行失败,将直接执行catch回调,如果成功,会执行第一个then方法,then方法里又通过isMoreThanFive返回了一个新的Promise实例,这时,如果新的Promise成功将执行第二个then方法,如果失败将执行catch方法。
末尾的catch方法会捕获到前面Promise出现的所有异常,包括reject返回的失败状态和代码执行过程中发生的错误,所以一般会省略then方法的第二个参数(处理失败时的回调),而直接在最后添加catch方法统一处理前面发生的错误。
在调用接口时,如果B接口依赖A接口返回的参数,这时,then的链式调用将非常方便。
Promise的all方法
先看代码
const p1 = isMoreThanFive() const p2 = isMoreThanFive() Promise.all([p1, p2]) .then(res => { console.log(res) }) .catch(err => { console.log(err) }) // 输出 failed 或者 ['success', 'success']
all方法接受一个数组,数组各项都是Promise实例。Promise.all会返回一个新的Promise实例,只有当数组内所有的Promise实例都成功时,all才返回成功状态,否则只要有一个失败都返回失败状态。如果all执行成功,将返回一个数组,数组的值和Promise实例的返回值一一对应。
注意,传给all方法的所有Promise实例都是同时执行的,也就是说同时执行多个异步操作,如果某个实例失败,则all方法直接返回失败状态,返回值由失败的Promise实例提供。
这在前端渲染页面时非常有用,利用Promise.all方法将可以同时请求多个接口,提升页面的渲染速度。
特殊情况,当Promise实例有自己的catch方法时:
const p1 = isMoreThanFive() const p2 = isMoreThanFive() .catch(err => { return err }) Promise.all([p1, p2]) .then(res => { console.log(res) }) .catch(err => { console.log(err) }) // 输出 failed 或者 ['success', 'success'] 或者 [ 'success', 'failed' ]
如果传入的Promise实例有自己的catch方法,就不会触发Promise.all的catch方法。p2如果返回失败,会被自己的catch方法捕获,经过catch处理后返回一个resolved成功的Promise实例,返回值是失败状态返回的err,最后Promise.all也就返回成功状态。
最后
单独使用Promise已经很方便很直观了,但是如果配合上async函数的话,写异步代码完全可以使用同步的写法,下一篇将会写关于async的东西。
关于Promise,大致就是这些,没有面面俱到,主要是把自己觉得经常用到的和重要的写了下来。如果想了解更多的细节和原理可以看阮一峰老师的ES6教程。