作者:小火柴@毛豆前端
ES6中的Promise对象是异步编程的一个重要的点,下面是我整理的学习笔记跟大家分享一下,在这之前我觉得有必要先了解一下JS事件机制和一些相关的异步操作。
JS事件机制
在说JS事件机制之前咱们先提一嘴浏览器进程。 浏览器是多进程运行的,JS引擎只是浏览器渲染进程中的一个单线程,在单线程中一次只能执行一个任务,多任务处理的情况下就要进行排队等候顺序执行,因此会出现若某任务执行很耗时,等待时间过长而卡死的情况,为了解决由于单线程特性出现的”卡死”问题,就用到了咱们今天要说的JS异步。 下图是浏览器进程思维导图:
JS引擎遇到一个异步事件后并不会一直等待其返回结果,而是将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件执行完毕并返回结果后,JS会将这个事件加入与当前执行栈不同的另一个队列–事件队列。被放入事件队列不会立即执行其回调,而是等待当前执行栈的所有任务执行完毕,主线程处于闲置状态时,主线程回去查找事件队列是否有任务。如果有,那么主线程会取出排在第一位的事件,并把该事件对应的回调放到执行栈中,然后执行其中的同步代码…,如此反复,这就形成了一个无线循环,也就是我们说的事件循环(Event Loop) 下图就是JS事件机制说明:
宏任务 & 微任务
事件循环过程是一个宏观的表述,由于异步任务之间并不相同,其执行优先级也有区别。因此不同的异步任务被分为宏任务和微任务两类。
运行机制: 1.执行一个宏任务(栈中没有就从事件队列中获取) 2.执行过程中如果遇到微任务,就将它添加到微任务的任务队列中 3.宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行) 4.当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染 5.渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
经典面试题:
setTimeout(() => { console.log(4); }, 0);
new Promise(resolve => {
console.log(1);
resolve()
console.log(2)
}).then(() => {
console.log(5)
})
console.log(3)
// 结果:1 2 3 5 4
Promise其实就是一个异步的微任务,那我们就开启Promise之旅吧。
Promise
简介
Promise 是异步编程的一种解决方案,比传统的解决方案 (回调函数和事件)更合理和更强大。简单说它就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上,Promise 是一个对象,从它可以获取异步操作的消息。Promise操作后返回的对象还是一个新的Promise对象,所以支持链式调用,它可以把异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,更便于理解与阅读。
Promise主要有以下特点
- Promise对象有不受外界影响的三个状态:
pending(进行中) fulfilled(已成功) rejected(已失败) 只有异步操作的结果才能确定当前处于哪种状态,任何其他操作都不能改变这个状态。这也是Promise(承诺)的由来。
- Promise状态一旦改变就不会再变,任何时候都可以得到这个结果。它的状态改变只有两种结果:
pending —-> fulfilled pending —-> rejected 只要有其中一种情况发生,状态就凝固了,不会再变,会一直得到这个结果,后续再添加Promise的回调函数也只能拿到前面状态凝固的结果
Promise缺点:
1.无法取消Promise,一旦新建它就会立即执行,无法中途取消
2.如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
3.当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
Promise API
从图中也可以看出来,Promise既是一个对象也是一个构造函数
Promise基本用法
resolve / reject
let promise = new Promise((resolve, reject) => {
if(/**操作成功 */){
resolve(seccess)
}else{
reject(error)
}
})
Promise接收一个函数作为参数,函数里有resolve和reject两个参数,这两个参数其实是Promise内置的两个方法,会在异步操作执行结束后调用,可以将异步操作的结果回传至回调函数,以确定Promise最终的一个状态(是fulfilled还是rejected)。 resolve方法的作用是将Promise的pending状态变为fulfilled,在异步操作成功之后调用,可以将异步返回的结果作为参数传递出去。 resolve还可以接受Promise实例作为参数
let p1 = new Promise((resolve, reject) => {
reject('error')
})
let p2 = new Promise((resolve, reject) => {
resolve(p1)
})
p2.then(s => console.log(s))
.catch(e => console.log(e))
reject方法的作用是将Promise的pending状态变为rejected,在异步操作失败之后调用,可以将异步返回的结果作为参数传递出去。 ⚠️他们之间只能有一个被执行,不会同时被执行,因为Promise只能保持一种状态。
then()
Promise实例确定后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数。
let promise = new Pormise();
promise.then(success => {
// 等同于上面的resolve(success)
}, error => {
// 注意:此处无法捕获onfulfilled抛出的错误
})
then(onfulfilled,onrejected)方法中有两个参数,两个参数都是函数,第一个参数执行的是resolve()方法(即异步成功后的回调方法),第二参数执行的是reject()方法(即异步失败后的回调方法)(第二个参数可选)。它返回的是一个新的Promise对象,因此可以采用链式写法(解决了异步串行的操作,避免了传统异步串行操作层层嵌套的问题)。
function createPromise(p, state){
return new Promise((resolve, reject) => {
setTimeout(() => {
if(state === 0){
reject(`error, ${p}`)
}else{
resolve(`success, ${p}`)
}
}, 0)
})
}
createPromise('p1', 1).then(success => {
console.log('111', success)
return createPromise('p2', 2)
}).then(success => {
console.log('222', success)
return createPromise('p3', 3)
}).then(success => {
console.log('333', success)
})
// 111 success, p1
// 222 success, p2
// 333 success, p3
⚠️then 方法注意点:简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise。
catch()
catch方法实际上是.then(null,onrejected)的别名,用于指定发生错误时的回调函数。作用和then中的onrejected一样,它还可以捕获onfulfilled抛出的错,弥补了then的第二个回调onrejected的缺陷
new Promise().then(success => {
// ...
}).catch(error => {
// ...
})
⚠️注意:串行操作时只能捕获前面Promise抛出的错,而无法捕获在他们后面的Promise抛出的错
createPromise('p1', 0).then(success => {
console.log('111', success)
return createPromise('p2', 0)
}).then(success => {
console.log('222', success)
return createPromise('p3', 0)
}).catch(error => {
console.log('333', error)
})
// 333 error, p1
finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。 该方法不接受任何参数,所以跟Promise的状态无关,不依赖于Promise的执行结果
createPromise('p1', 1).then(success => {
console.log('111', success)
}).catch(error => {
console.log('222', error)
}).finally(() => {
console.log('finally')
})
// 111 success, p1
// finally
all()
Promise.all方法接受一个以Promise实例组成的数组作为参数。Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作都执行完毕后才执行回调,只要其中一个异步操作返回的状态为rejected那么Promise.all()返回的Promise即为rejected状态,此时第一个被reject的实例的返回值,会传递给Promise.all的回调函数:
Promise
.all([createPromise('p1', 1), createPromise('p2', 1)])
.then(r => { console.log(r) })
// ["success, p1", "success, p2"]
Promise
.all([createPromise('p1', 1), createPromise('p2', 0)])
.then(r => { console.log(r) })
.catch(e => { console.log(e) })
// error, p2
若Promise.all的Promise实例参数自己定义了catch方法且被rejected,就不会触发Promise.all()的catch方法了,而是执行了then
let p2 = createPromise('p2', 0).catch(e => {
console.log('p2-catch', e)
})
Promise
.all([createPromise('p1', 1), p2])
.then(r => { console.log(r) })
.catch(e => { console.log(e) })
// p2-catch error, p2
// ["success, p1", undefined]
race()
Promise的race方法和all方法类似,区别在于all方法的效果实际上是(谁慢以谁为准),而race方法则是(谁快以谁为准)
Promise
.race([createPromise('p1', 1), createPromise('p2', 0)])
.then(r => { console.log(r) })
.catch(e => { console.log(e) })
// success, p1