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
}
贡献者: mankueng