作者:小火柴@毛豆前端

ES6中的Promise对象是异步编程的一个重要的点,下面是我整理的学习笔记跟大家分享一下,在这之前我觉得有必要先了解一下JS事件机制和一些相关的异步操作。

JS事件机制

在说JS事件机制之前咱们先提一嘴浏览器进程。 浏览器是多进程运行的,JS引擎只是浏览器渲染进程中的一个单线程,在单线程中一次只能执行一个任务,多任务处理的情况下就要进行排队等候顺序执行,因此会出现若某任务执行很耗时,等待时间过长而卡死的情况,为了解决由于单线程特性出现的”卡死”问题,就用到了咱们今天要说的JS异步。 下图是浏览器进程思维导图: 1.png-581.9kB

JS引擎遇到一个异步事件后并不会一直等待其返回结果,而是将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件执行完毕并返回结果后,JS会将这个事件加入与当前执行栈不同的另一个队列–事件队列。被放入事件队列不会立即执行其回调,而是等待当前执行栈的所有任务执行完毕,主线程处于闲置状态时,主线程回去查找事件队列是否有任务。如果有,那么主线程会取出排在第一位的事件,并把该事件对应的回调放到执行栈中,然后执行其中的同步代码…,如此反复,这就形成了一个无线循环,也就是我们说的事件循环(Event Loop) 下图就是JS事件机制说明: image.png-83.4kB

宏任务 & 微任务

事件循环过程是一个宏观的表述,由于异步任务之间并不相同,其执行优先级也有区别。因此不同的异步任务被分为宏任务和微任务两类。 image.png-41kB

运行机制: 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主要有以下特点

  1. Promise对象有不受外界影响的三个状态:

pending(进行中) fulfilled(已成功) rejected(已失败) 只有异步操作的结果才能确定当前处于哪种状态,任何其他操作都不能改变这个状态。这也是Promise(承诺)的由来。

  1. Promise状态一旦改变就不会再变,任何时候都可以得到这个结果。它的状态改变只有两种结果:

pending —-> fulfilled pending —-> rejected 只要有其中一种情况发生,状态就凝固了,不会再变,会一直得到这个结果,后续再添加Promise的回调函数也只能拿到前面状态凝固的结果

Promise缺点:

1.无法取消Promise,一旦新建它就会立即执行,无法中途取消

2.如果不设置回调函数,Promise内部抛出的错误,不会反应到外部

3.当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

Promise API

从图中也可以看出来,Promise既是一个对象也是一个构造函数 image.png-35.5kB

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

毛豆前端团队

never stop, always on the way!