ES6之Promise再解读

做者:小火柴@毛豆前端前端

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

JS事件机制

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

1.png-581.9kB

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

image.png-83.4kB

宏任务 & 微任务

事件循环过程是一个宏观的表述,因为异步任务之间并不相同,其执行优先级也有区别。所以不一样的异步任务被分为宏任务和微任务两类。 编程

image.png-41kB

运行机制: 1.执行一个宏任务(栈中没有就从事件队列中获取) 2.执行过程当中若是遇到微任务,就将它添加到微任务的任务队列中 3.宏任务执行完毕后,当即执行当前微任务队列中的全部微任务(依次执行) 4.当前宏任务执行完毕,开始检查渲染,而后GUI线程接管渲染 5.渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)数组

经典面试题:promise

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

简介bash

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
复制代码
相关文章
相关标签/搜索