ES6-Promise基础篇

293 阅读7分钟

前言

浏览器渲染网页时创建一个渲染进程来对页面进行渲染。在渲染进程中包含了GUI渲染线程和JS引擎线程,且这两个线程时互斥的。

学习Promise之前先来了解下什么是 回调地狱

function ajax(url, successCallBack, failCallBack) {
    // successCallBack是成功回调函数
    // failCallBack是失败回调函数
}

ajax(url1, res1=> {
    ajax(url2, res2 => {
        ajax(url3, res3 => {
            ajax...
            // fn(res1, res2, res3)
        })
    })
})

上述代码中,ajax是一个异步请求的封装函,第二个和第三个参数就是其成功和失败的回调函数。在调用ajax方法代码中,第三个ajax的调用依赖第一和第二个ajax的结果。

比上面的3层嵌套更多的深度嵌套的回调函数称之为 回调地狱

而使用Promise来处理这样的问题具有明显的优势:

function request(url) {
    return new Promise((resolve, reject) => {
        ajax(url, res => {
            resolve(res)
        }, err => {
            reject(err)
        }
    })
}

Promise.all([request(url1), request(url2), request(url3)])
.then(res => {
    fn(...res)
}).catch(err => {
    // 
})

对请求进行封装,不再关注每一步请求的逻辑处理,而专注于业务逻辑的处理,提高了开发效率。

期约-Promise

Promise是ES6中新增的一个类,通过 new 操作符来实例化。

语法:let p = new Promise((resolve, reject) => {})

上面语法中参数是一个执行器函数,如果不提供一个执行器函数,则会抛出SyntaxError

Promise实例是一个有状态的对象,也称为期约状态机,可能处于以下3种状态的其中一个:

  • 待定(pending)
  • 解决(resolve,也称为兑现fulfilled)
  • 拒绝(rejected)

pending是promise的初始状态,promise可能永远处于pending状态,也可能从pending到resolve,或者从pending到reject。只要到达resolve或reject后,promise的状态就不可逆,即不再变化。

Promise的状态是私有的,即Promise的外部代码不能读取和修改Promise的状态,JavaScript也检测不到。这主要是为了避免Promise外部代码根据Promise的状态来以同步方式处理代码。

Promise表示的是一个异步操作

执行器函数

执行器函数就是传入到创建Promise实例时传入的方法。

执行器函数有两个职责

  • 初始化Promise的异步行为:因此原因,执行器函数是同步执行的。
  • 控制Promise状态的最终转换
    • 执行器函数的第一个参数resolve方法是把状态切换成解决,即完成
    • 执行器函数的第二个参数reject方法是把状态切换成拒绝,即失败
    Promise封装一个异步操作:
    let p1 = new Promise((resolve, reject) => {
        ajax(url, successCallback(res) {
            // 成功时把状态切换为resolve
            resolve(res)
            }, failCallback(err) {
            // 失败时把状态切换成reject
            reject(err)
        })
    })
    

Promise.resolve([arg])

功能:返回一个已解决的Promise实例,arg是可选的,arg可以是任何非promise值,也可以是promise对象

let p1 = new Promise((resolve, reject) => resolve())
let p2 = Promise.resolve()

上面两种方法都能返回一个已解决的promise。

和执行器函数内部的resolve()方法一样,Promise.resolve()默认返回的promise结果都是undefined,如下图所示:

image.png

Promise.resolve()resolve()都可以把任何非promise值转换为一个Promise实例,并且两者都只接收一个参数,多余的参数会被忽略

let p4 = Promise.resolve(3)
let p5 = new Promise(resolve => {
    resolve(3)
})

console.log(p4, p5) // 两个方法的执行结果一样,如下图

image.png

Promise.resolve()方法是一个幂等方法,即如果Promise.resolve()传入的参数是一个promise,那这个行为类似于空包装,返回值仍然是这个传入的参数。

let p6 = Promise.resolve(3)
let p7 = Promise.resolve(p6)
p6 === p7 // true

Promise.reject([arg])

功能:返回一个已拒绝的Promise实例,并抛出一个异步错误,并且这个异步错误不能通过try/catch捕获,只能通过拒绝处理程序捕获。

执行器函数的reject()Promise.reject()都能返回一个已拒绝的promise实例:

let p8 = new Promise((resolve, reject) => {
    reject()
})
let p9 = Promise.reject()

执行上面两种方法的代码时,都会抛出一个异步错误,如下图: image.png

然后p8和p9都等于(这里p8和p9是不相等的,参考创建的两个对象):

image.png

Promise.reject()reject()也只接收一个参数,多余的参数都会被忽略。

Promise.reject()reject()也都可以把任何非promise的值包装成一个已拒绝的promise,但是两者都不是幂等方法,如果给这个方法传入一个promise对象,则这两个方法会把这个promise对象当作拒绝的理由,也就是上图中的 [[PromiseResult]]

p9 = Promise.reject(p8)
console.log(p9) // 如下图

image.png

Promise实例方法

Promise实例方法是连接外部同步代码与内部异步代码之间的桥梁。

在ECMAScript暴露的异步结构中,任何对象都有一个 then() 方法,这个方法实现了 Thenable 接口。

Promise.prototype.then()

Promise.prototype.then() 是为Promise实例添加处理程序的主要方法。

这个方法接收两个参数:第一个参数是onResolved的处理函数,第二个参数是onRejected的处理函数。

let p11 = new Promise((resolve, reject) => { ... })
p11.then(() => {
    // onResolved 处理函数
}, () => {
    // onRejected 处理函数
})

参数解析

  • 传任何非函数类型的参数都会被静默忽略掉。
  • 如果只要onRejected处理函数,则在第一个参数位置上传入 undefined

返回值:then() 方法返回一个新的Promise实例

let p12 = new Promise(() => {})
let p13 = p12.then()

setTimeout(console.log, 0, p12) // Promise <pending>
setTimeout(console.log, 0, p13) // Promise <pending>
setTimeout(console.log, 0, p12 === p13) // false
  • 这个新的Promise实例是基于 onResolved 返回值构建,即 onResolved 函数的返回值会被Promise.resolve()包装生成一个新的Promise实例返回。
  • 如果没有提供 onResolvedPromise.resolve() 会包装上一个期约解决之后的值
  • 如果有onResolved,当没有显示的返回语句,则Promise.resolve()会包装默认的返回值undefined
  • 如果抛出异常(throw),则会返回拒绝的Promise
  • 如果返回错误信息(Error),则会把这个错误信息包装在一个已解决Promise里作为结果一起返回
let p1 = new Promise(resolve => {
    resolve(1)
})
console.log(p1) // Promise <fulfilled> [[PromiseResult]]: 1

// 没有 onResolved 处理函数,Promise包装上一个已解决期约的值 1
let p2 = p1.then()
console.log(p2) // Promise <fulfilled> [[PromiseResult]]: 1
p1 === p2 // false

// 有 onResolved 处理函数,但没有显示的返回值,Promise.resolve()包装默认返回值 undefined
let p3 = p1.then(res => {
    console.log(res) // 1
})
console.log(p3) // Promise <fulfilled> [[PromiseResult]]: undefined

// 有 onResolved 处理程序,并且有显示的返回值,则Promise.resolve()包装这个返回值
let p4 = p1.then(res => {
    return res + 1
})
console.log(p4) // Promise <fulfilled> [[PromiseResult]]: 2

// 抛出异常,返回一个拒绝的Promise
let p5 = p1.then(res => {
    throw 'aaa'
})
let p6 = p5.catch(err => {
    console.log(err) // aaa
})
console.log(p5) // Promise <rejected> [[PromiseResult]] aaa
console.log(p6) // Promise <fulfilled> [[PromiseResult]]: undefined

// 返回错误信息,不会触发拒绝Promise
let p7 = p1.then(res => {
    return Error('www')
})
console.log(p7) // Promise <fulfilled> [[PromiseResult]]: Error: www

onRejected处理函数和onResolved函数类似,拒绝处理函数是捕获错误后不抛出异常,然后Promise.resolve()方法返回一个已解决的期约。

Promise.prototype.catch()

Promise.prototype.catch() 是为Promise实例添加拒绝处理程序。

这个方法只接收一个参数:onRejected处理函数,事实上,这个实例方法是一个语法糖,相当于Promise.prototype.then(null, onRejected)

Promise.prototype.finally()

Promise实例添加 onFinally 处理程序,在Promise状态变换resolvedrejected都会执行。这个方法没有任何参数进来,因此没办法知道Promise的状态。这个方法主要用来添加清理代码。

  • 处理程序返回以下数值时,返回值都是父Promise对象
let p1 = Promise.resolve('abc')
console.log(p1) // Promise <resolved>: 'abc'

let p2 = p1.finally()
let p3 = p1.finally(() => undefined)
let p4 = p1.finally(() => {})
let p5 = p1.finally(() => Promise.resolve())
let p6 = p1.finally(() => Promise.resolve('bbb'))
let p7 = p1.finally(() => 'ccc')
let p8 = p1.finally(() => Error('ddd')

let p11 = Promise.reject('abc')

let p12 = p11.finally()
let p13 = p11.finally(() => {})
let p14 = p11.finally(() => Promise.resolve())
let p15 = p11.finally(() => Promise.resolve('bbb'))
let p16 = p11.finally(() => 'ccc')
let p17 = p11.finally(() => Error('ddd'))
let p18 = p11.finally(() => undefined)

上面代码中,p2~p8都是 Promise <resolved>: abc,p12~p18都是 Promise <rejected>: abc

  • 处理程序返回一个待定Promise对象,返回值是待定的Promise对象
let p9 = p1.finally(() => new Promise(() => {}))
console.log(p9) // Promise <pending>
  • 处理程序返回一个已拒绝的Promise对象,返回值是已拒绝的Promise对象
let p19 = p1.finally(() => Promise.reject())
// Uncaught (in Promise): undefined
console.log(p19) // Promise <rejected>: undefined
  • 处理程序显示抛出异常(throw),返回值是已拒绝的Promise对象
let p20 = p1.finally(() => throw 'aaa')
// Uncaught (in Promise) aaa
console.log(20) // Promise <rejected>: 'aaa'

Promise特点

  • `非重入Promise方法

当Promise状态改变时,与状态相关的处理程序仅仅会被排期,而不是立即执行。而跟在这个处理程序代码之后的同步代码一定会在处理程序之前执行。

这个特点适用于onResolved/onRejected处理程序、catch处理程序和finally处理程序。

  • 邻近处理程序的执行顺序

当给Promise添加多个处理程序,则按添加的顺序依次执行。

适用于then()catch()finally()