Promise
异步状态
状态说明
Promise包含pending、fulfilled、rejected三种状态
- pending 指初始等待状态,初始化 promise 时的状态
- resolve 指已经解决,将 promise 状态设置为fulfilled
- reject 指拒绝处理,将 promise 状态设置为rejected
- promise 是生产者,通过 resolve 与 reject 函数告之结果
- promise 非常适合需要一定执行时间的异步任务
- 状态一旦改变将不可更改
then
个promise 需要提供一个then方法访问promise 结果,then 用于定义当 promise 状态发生改变时的处理,即promise处理异步操作,then 用于结果。
- then 方法必须返回 promise,用户返回或系统自动返回
- 第一个函数在resolved 状态时执行,即执行resolve时执行then第一个函数处理成功状态
- 第二个函数在rejected状态时执行,即执行reject 时执行第二个函数处理失败状态,该函数是可选的
- 两个函数都接收 promise 传出的值做为参数
- 也可以使用catch 来处理失败的状态
- 如果 then 返回 promise ,下一个then 会在当前promise 状态改变后执行
语法说明
then的语法如下,onFulfilled 函数处理 fulfilled 状态, onRejected函数处理 rejected 状态
- onFulfilled 或 onRejected 不是函数将被忽略
- 两个函数只会被调用一次
- onFulfilled 在 promise 执行成功时调用
- onRejected 在 promise 执行拒绝时调用
promise.then(onFulfilled, onRejected)
基础知识
then 会在 promise 执行完成后执行,then 第一个函数在 resolve成功状态执行
const promise = new Promise((resolve, reject) => {
resolve('success')
}).then(value => {
console.log(`解决:${value}`)
}, reason => {
console.log(`拒绝:${reason}`)
})
如果只关心成功则不需要传递 then 的第二个参数 如果只关心失败时状态,then 的第一个参数传递 null
promise 传向then的传递值,如果then没有可处理函数,会一直向后传递
const promise = new Promise((resolve, reject) => {
reject('error')
})
.then()
.then(null, err => {
console.log(err)
})
链式调用
每次的then都是一个全新的 promise,默认then返回的promise状态是fulfilled
const promise = new Promise((resolve, reject) => {
resolve('fulfilled')
})
.then(resolve => {
console.log(resolve)
})
.then(resolve => {
console.log(resolve)
})
每次的then都是一个全新的promise,不要认为上一个promise状态会影响以后then返回的状态
const p1 = new Promise(resolve => {
resolve()
})
const p2 = p1.then(() => {
console.log('mkimq')
})
p2.then(() => {
console.log('mkimq.com')
})
console.log(p1)
console.log(p2)
setTimeout(() => {
console.log(p1)
console.log(p2)
})
then是对上个promise的rejected的处理,每个then会是一个新的promise,默认传递fulfilled状态
new Promise((resolve, reject) => {
reject()
})
.then(
resolve => console.log('fulfilled'),
reject => console.log('reject')
)
.then(
resolve => console.log('fulfilled'),
reject => console.log('reject')
)
.then(
resolve => console.log('fulfilled'),
reject => console.log('reject')
)
如果内部返回promise时将使用该promise
const p1 = new Promise(resolve => {
resolve()
})
const p2 = p1.then(() => {
return new Promise(r => {
r('mkimq.com')
})
})
p2.then(v => {
console.log(v)
})
如果then返回promise时,后面的then就是对返回的promise的处理,需要等待该promise变更状态后执行。
const promise = new Promise(resolve => resolve())
const p1 = promise.then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log('p1')
resolve()
}, 2000)
})
}).then(() => {
return new Promise((a, b) => {
console.log('p2')
})
})
其它类型
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。
循环调用
如果 then 返回与 promise 相同将禁止执行
反面例子
const p1 = new Promise(resolve => {
resolve()
})
const p2 = p1.then(() => {
return p2
})
// TypeError: Chaining cycle detected for promise
promise
如果返加值是 promise 对象,则需要更新状态后,才可以继承执行后面的promise
new Promise((resolve, reject) => {
resolve(
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('解决状态')
}, 2000)
})
)
})
.then(
v => {
console.log(`fulfilled: ${v}`)
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('拒绝状态')
}, 2000)
})
},
v => {
console.log(`rejected: ${v}`)
}
)
.catch(error => console.log(`rejected: ${error}`))
Thenables
包含 then 方法的对象就是一个 promise ,系统将传递 resolvePromise 与 rejectPromise 做为函数参数
下例中使用 resolve 或在then 方法中返回了具有 then方法的对象
- 该对象即为 promise 要先执行,并在方法内部更改状态
- 如果不更改状态,后面的 then promise都为等待状态
new Promise((resolve, reject) => {
resolve({
then(resolve, reject) {
resolve('解决状态')
}
})
})
.then(v => {
console.log(`fulfilled: ${v}`)
return {
then(resolve, reject) {
setTimeout(() => {
reject('失败状态')
}, 2000)
}
}
})
.then(null, error => {
console.log(`rejected: ${error}`)
})
包含 then 方法的对象可以当作 promise来使用
class User {
constructor(id) {
this.id = id
}
then(resolve, reject) {
resolve(ajax(`https://www.mkimq/user?id=${this.id}`))
}
}
new Promise((resolve, reject) => {
resolve(ajax(`https://www.mkimq/php/user?name=mkimq`))
})
.then(user => {
return new User(user.id)
})
.then(lessons => {
console.log(lessons)
})
当然也可以是类
new Promise((resolve, reject) => {
resolve(
class {
static then(resolve, reject) {
setTimeout(() => {
resolve('解决状态')
}, 2000)
}
}
)
}).then(
v => {
console.log(`fulfilled: ${v}`)
},
v => {
console.log(`rejected: ${v}`)
}
)
如果对象中的 then 不是函数,则将对象做为值传递
new Promise((resolve, reject) => {
resolve()
})
.then(() => {
return {
then: 'mkimq'
}
})
.then(v => {
console.log(v) //{then: 'mkimq'}
})
catch
下面使用未定义的变量同样会触发失败状态
const p = new Promise((resolve, reject) => {
mk
}).then(
null,
err => console.log(err)
)
如果 onFulfilled 或 onRejected 抛出异常,则 p2 拒绝执行并返回拒因
const p = new Promise((resolve, reject) => {
throw new Error('fail')
})
const p2 = p.then()
p2.then().then(null, err => {
console.log(err)
})
catch用于失败状态的处理函数,等同于 then(null,reject){}
- 建议使用 catch 处理错误
- 将 catch 放在最后面用于统一处理前面发生的错误
- catch 也可以捕获对 then 抛出的错误处理
- catch 也可以捕获其他错误
使用建议
建议将错误要交给catch处理而不是在then中完成
处理机制
在promise中抛出的错误也会被catch捕获
在 catch 中发生的错误也会抛给最近的错误处理
异步中 throw 将不会触发 catch
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error('fail')
}, 2000)
}).catch(msg => {
console.log(msg + 'mkimq')
})
定制错误
可以根据不同的错误类型进行定制操作,下面将参数错误与404错误分别进行了处理
class ParamError extends Error {
constructor(msg) {
super(msg)
this.name = 'ParamError'
}
}
class HttpError extends Error {
constructor(msg) {
super(msg)
this.name = 'HttpError'
}
}
function ajax(url) {
return new Promise((resolve, reject) => {
if (!/^http/.test(url)) {
throw new ParamError('请求地址格式错误~')
}
const xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.send()
xhr.onload = function() {
if (this.status == 200) {
resolve(JSON.parse(this.response))
} else if (this.status == 404) {
reject(new HttpError('不存在'))
} else {
reject('加载失败')
}
}
xhr.onerror = function() {
reject(this)
}
})
}
ajax('https://www.baidu.com')
.then(v => {
console.log(v)
})
.catch(err => {
if (err instanceof ParamError) {
console.log(err.message)
}
if (err instanceof HttpError) {
console.log(err.message)
}
console.log(err)
})
事件处理
unhandledrejection事件用于捕获到未处理的Promise错误,下面的 then 产生了错误,但没有catch 处理,这时就会触发事件。该事件有可能在以后被废除,处理方式是对没有处理的错误直接终止。
window.addEventListener('unhandledrejection', function (event) {
console.log(event.promise) // 产生错误的promise对象
console.log(event.reason) // Promise的reason
})
new Promise((resolve, reject) => {
resolve('success')
}).then(msg => {
throw new Error('fail')
})
finally
无论状态是resolve 或 reject 都会执行此动作,finally与状态无关。
new Promise((resolve, reject) => {
setTimeout(() => {
reject('mkimq error!')
}, 2000)
})
.then(msg => {
console.log('resolve')
})
.catch(msg => {
console.log('reject')
})
.finally(() => {
console.log('finally')
})
实例操作
异步请求
function ajax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.send()
xhr.onload = function() {
if (this.status == 200) {
resolve(JSON.parse(this.response))
} else {
reject(this)
}
}
xhr.onerror = function() {
reject(this)
}
})
}
图片加载
function loadImage(file) {
return new Promise((resolve, reject) => {
const image = new Image()
image.src = file
image.onload = () => {
resolve(image)
}
image.onerror = reject
})
}
定时器
function timeout(times) {
return new Promise(resolve => {
setTimeout(resolve, times)
})
}
链式操作
- 第个 then 都是一个promise
- 如果 then 返回 promse,只当promise 结束后,才会继承执行下一个 then
链式加载
function load(file) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = file
script.onload = () => resolve(script)
script.onerror = () => reject()
document.body.appendChild(script)
})
}
load('js/mk.js')
.then(() => load('js/mankeung.js'))
.then(() => {
mk()
})
扩展接口
resolve
使用 promise.resolve 方法可以快速的返回一个promise对象
根据值返加 promise
Promise.resolve('mkimq')
.then(v => {
console.log(v)
})
reject
和 Promise.resolve 类似,reject 生成一个失败的promise
Promise.reject('mkimq')
.catch(v => {
console.log(v)
})
all
使用Promise.all 方法可以同时执行多个并行异步操作,比如页面加载时同进获取课程列表与推荐课程。
- 任何一个 Promise 执行失败就会调用 catch方法
- 适用于一次发送多个异步操作
- 参数必须是可迭代类型,如Array/Set
- 成功后返回 promise 结果的有序数组
allSettled
allSettled 用于处理多个promise ,只关注执行完成,不关注是否全部执行成功,allSettled 状态只会是fulfilled。
race
使用Promise.race() 处理容错异步,和race单词一样哪个Promise快用哪个,哪个先返回用哪个。
- 以最快返回的promise为准
- 如果最快返加的状态为rejected 那整个promise为rejected执行cache
- 如果参数不是promise,内部将自动转为promise
任务队列
实现原理
如果 then 返回promise 时,后面的then 就是对返回的 promise 的处理
const p = Promise.resolve()
const p2 = p.then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log('p1')
resolve()
}, 1000)
})
})
p2.then(() => {
return new Promise((a, b) => {
setTimeout(() => {
console.log('p2')
}, 1000)
})
})
下面使用 map 构建的队列,有以下几点需要说明
- then 内部返回的 promise 更改外部的 promise 变量
- 为了让任务继承,执行完任务需要将 promise 状态修改为 fulfilled
function queue(nums) {
let p = Promise.resolve()
nums.map(n => {
p = p.then(v => {
return new Promise(resolve => {
console.log(n)
resolve()
})
})
})
}
queue([1, 2, 3, 4, 5])
下面再来通过 reduce 来实现队列
function queue(nums) {
return nums.reduce((p, n) => {
return p.then(() => {
return new Promise(resolve => {
console.log(n)
resolve()
})
})
}, Promise.resolve())
}
queue([1, 2, 3, 4, 5])
封装
export default function(promise) {
promise.reduce((promise, next) => promise.then(next), Promise.resolve())
}
queue([1, 2, 3].map(v => axios(`/api/?id=${v}`).then(d => console.log(d))))
async/await
使用 async/await 是promise 的语法糖,可以让编写 promise 更清晰易懂,也是推荐编写promise 的方式。
- async/await 本质还是promise,只是更简洁的语法糖书写
- async/await 使用更清晰的promise来替换 promise.then/catch 的方式
async
函数前加上async,函数将返回promise,我们就可以像使用标准Promise一样使用了。
async function fn() {
return 'mkimq'
}
fn().then(d => {
console.log(d)
})
await
使用 await 关键词后会等待promise 完
- await 后面一般是promise,如果不是直接返回
- await 必须放在 async 定义的函数中使用
- await 用于替代 then 使编码更优雅
类中使用
和 promise 一样,await 也可以操作thenables 对象
class User {
constructor(name) {
this.name = name
}
then(resolve, reject) {
const d = ajax('/api')
resolve(d)
}
}
async function get() {
const d = await new User('mkimq')
console.log(d)
}
get()
类方法也可以通过 async 与 await 来操作promise
其他声明
函数声明
async function fn() {
return await ajax('/api')
}
fn().then(d => {
console.log(d)
})
函数表达式
const fn = async function() {
return await ajax('/api')
}
fn().then(d => {
console.log(d)
})
对象方法声明
const mk = {
async fn() {
return await ajax('/api')
}
}
mk.fn().then(d => {
console.log(d)
})
立即执行函数
(async () => {
const d = await ajax('/api')
console.log(d)
})()
类方法中的使用
class User {
async get() {
return await ajax('/api')
}
}
new User().get().then(d => {
console.log(d)
})
错误处理
async 内部发生的错误,会将必变promise对象为rejected 状态,所以可以使用catch 来处理
async function fn() {
console.log(mkimq)
}
fn().catch(err => {
throw new Error(err)
})
- try...catch 处理错误
- 多个 await 时当前面的出现失败,后面的将不可以执行
- 如果对前一个错误进行了处理,后面的 await 可以继续执行
- 也可以使用 try...catch 特性忽略不必要的错误
- 也可以将多个 await 放在 try...catch 中统一处理错误
并发执行
有时需要多个await 同时执行,有以下几种方法处理,下面多个await 将产生等待
async function p1() {
return new Promise(resolve => {
setTimeout(() => {
console.log('p1')
resolve()
}, 2000)
})
}
async function p2() {
return new Promise(resolve => {
setTimeout(() => {
console.log('p2')
resolve()
}, 1000)
})
}
async function fn() {
await p1()
await p2()
}
fn()
使用 Promise.all() 处理多个promise并行执行
async function fn() {
await Promise.all([p1(), p2()])
}
fn()
让promise先执行后再使用await处理结果
async function fn() {
const p11 = p1()
const p21 = p2()
await p11
await p21
}