Skip to content
On this page

关于 JS 的异步

Promise 基本使用

js
log(new Promise((resolve, reject) => {})) //Promise {<pending>}
log(new Promise((resolve, reject) => {
	resolve('yes') // Promise {<resolved>: "no"}
})) 
log(new Promise((resolve, reject) => {
	reject('no') // Promise {<rejected>: "no"}
})) 
// 成功状态和拒绝状态如何处理?在后面使用 then ,then 接受两个回调函数(一个是成功回调,一个是失败回调)
new Promise((resolve, reject) => {
	// resolve('yes')
	reject('no')
}).then(value => {
	console.log(value)
}, reason => {
	console.log(reason)
})

宏任务和微任务执行顺序

图片加载、文件加载、定时器、网络请求这些属于宏任务,Promise 属于微任务

js
// 案例1
setTimeout(() => {
	console.log('setTimeout')
}, 0)
new Promise(resolve => {
	console.log('promise')
	resolve()
}).then(value => {
	console.log('then1')
	return new Promise(resolve => {
		console.log(1)
		resolve()
	})
}).then(value => {
	console.log('then2')
})
console.log('main') // 这行属于同步任务
// 打印顺序如下
// promise > main > then1 > 1 > then2 > setTimeout

// 案例2
setTimeout(() => {
	console.log('setTimeout')
}, 0)
new Promise(resolve => {
	console.log('promise')
	resolve()
}).then(value => {
	console.log('then1')
	setTimeout(() => {
        return new Promise(resolve => {
    		console.log(1)
    		resolve()
    	}).then(value => {
		    console.log('then3')
    	})
    }, 0)
}).then(value => {
	console.log('then2')
})
console.log('main')
// 打印顺序如下
// promise > main > then1 > then2 > setTimeout > 1 > then3

同步任务优先级最高,Promise 的回调函数也属于同步的,then 只有等到 promise 状态改变被通知后才加入到 微任务队列 里,同步任务(可以理解为主线程)执行完毕后去检测微任务队列并执行里面的微任务,微任务队列都执行完毕后,检测宏任务队列并执行里面的宏任务。

js
// 案例3
let promise = new Promise(resolve => {
	setTimeout(() => {
		console.log('setTimeout')
		resolve()
	}, 0)
	console.log('promise')
}).then(value => {
	console.log('then1')
	setTimeout(() => {
        return new Promise(resolve => {
    		console.log(1)
    		resolve()
    	})
    }, 0)
}).then(value => {
	console.log('then2')
})
console.log('main')
// 打印顺序如下
// promise > main > setTimeout > then1 > then2 > 1

Promise 单一状态和状态切换

js
let p1 = new Promise((resolve, reject) => {
	// resolve('yes')
	// reject('no')
	setTimeout(() => {
		reject('no')
	}, 3000)
})
new Promise((resolve, reject) => {
	resolve(p1)
	reject('error') // 没有作用
}).then(ok => {
	console.log(ok)
}, no => {
	console.log(no)
})
// resolve 的值是 promise 的话会影响后面 then 的执行
// 结果:等待了三秒,打印出 no
// promise 状态改变后是不可逆的

then 的基本用法

js
new Promise((resolve, reject) => {
	// resolve('yes')
	reject('no')
})
.then() // 这里不接受回调,但是可以向后面传递
.then(null, reason => {
	console.log(reason)
})

then 的返回值

then 方法返回的是一个 promise ,其状态根据 then 中的回调函数来决定。 [then 的返回值](Promise.prototype.then() - JavaScript | MDN (mozilla.org))

js
// 案例 4
let p1 = new Promise((resolve, reject) => {
	resolve('ok')
})
let p2 = p1.then(ok => {
	console.log(ok)
}, reason => {
	console.log(reason)
})
setTimeout(() => {
	console.log(p1)
	console.log(p2)
})
// 结果如下
// ok > Promise {<resolved>: "ok"} > Promise {<resolved>}

then 返回其他类型的 Promise 封装

主要是实现:thenable 接口。

js
// 案例 1
let p1 = new Promise((resolve, reject) => {
	resolve('ok1')
}).then(ok => {
	console.log(ok)
	return {
		then(resolve, reject) {
			console.log('ok2')
			// resolve('ok3')
			setTimeout(() => {
				resolve('ok3')
			}, 1000)
		}
	}
}, reason => {
	console.log(reason)
}).then(ok => {
	console.log(ok)
})

// 案例 2
let p1 = new Promise((resolve, reject) => {
	resolve('ok1')
}).then(ok => {
	console.log(ok)
	class Then {
		then(resolve, reject) {
			console.log('ok2')
			// resolve('ok3')
			setTimeout(() => {
				resolve('ok3')
			}, 1000)
		}
	}
	return new Then()
}, reason => {
	console.log(reason)
}).then(ok => {
	console.log(ok)
})

// 案例 3
let p1 = new Promise((resolve, reject) => {
	resolve('ok1')
}).then(ok => {
	console.log(ok)
	return class {
		static then(resolve, reject) {
			console.log('ok2')
			// resolve('ok3')
			setTimeout(() => {
				resolve('ok3')
			}, 1000)
		}
	}
}, reason => {
	console.log(reason)
}).then(ok => {
	console.log(ok)
})

使用 Promise 实现 get 请求

js
function ajax(url) {
	return new Promise((resolve, reject) => {
		let xhr = new XMLHttpRequest();
		xhr.open('GET', url)
		xhr.send()
		xhr.onload = function() {
			if(this.status === 200) {
				resolve(JSON.parse(this.response))
			} else {
				reject('error')
			}
		}
		xhr.onerror = function() {
			reject('error')
		}
	})
}

Promise 错误处理与 catch

catch 返回值是 Promise,其状态根据catch 内的回调函数执行情况决定。

js
new Promise((resolve, reject) => {
	resolve('ok')
	// reject(new Error('fail'))
	// throw new Error('fail')
	// count + 1 // 系统自动抛出这行错误
	/*
	try {
		count + 1
	} catch(error) {
		reject(error)
	}
	*/
})
.then(ok => {
	console.log(ok)
	return new Promise((resolve, reject) => {
		reject('fail')
	})
}, reason => {
	console.log(reason)
})
.then(ok => {
	console.log(ok)
})
.catch(err => {
	console.log('error: ', err)
})

[MDN 文档描述 Promise catch](Promise.prototype.catch() - JavaScript | MDN (mozilla.org))

Promise 自定义错误处理

js
ajax(url)
.then(res => {
	console.log(res)
})
.catch(err => {
	if(err instanceof ParamError) {
		console.log(err.msg)
	}
	if(err instanceof HttpError) {
		alert(err.msg)
	}
})


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('url 不合规')
		}
		let 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('error')
			}
		}
		xhr.onerror = function() {
			reject('error')
		}
	})
}

Promise.prototype.finally() (ES9/ES2018)

js
new Promise((resolve, reject) => {
	reject('no')
})
.then(ok => {
	console.log(ok)
})
.catch(err => {
	console.log(err)
})
.finally(() => {
	console.log('finally')
})

图片加载和 Promise 结合

js
function loadImg(src) {
	return new Promise((resolve, reject) => {
		const image = new Image()
		image.src = src
		image.onload = () => {
			resolve(image)
		}
		image.onerror = reject
		document.body.appendChild(image)
	})
}
loadImage('xxx/xx.png').then(image => {
	image.style.border = '2px solid red'
})

封装 定时器

js
function timeout(delay = 1000) {
	return new Promise(resolve => setTimeout(resolve, delay))
}
timeout(2000).then(() => {
	console.log('over1')
	return timeout(300)
}).then(() => {
	console.log('over2')
})
// ---------------------------- //
function interval(delay = 1000, cb) {
	return new Promise(resolve => {
		let id = setInterval(() => {
			cb(id, resolve)
		}, delay)
	})
}
interval(2000, (timerId, resolve) => {
	console.log('callback')
	clearInterval(timerId)
	resolve('finish1')
}).then(value => {
	console.log(value)
	return interval(100, (timerId, resolve) => {
		clearInterval(timerId)
		resolve('finish2')
	})
}).then(res => {
	console.log(res)
})
// 启发:使用 setInterval 实现 setTimeout 功能;

script 的异步加载

js
function loadScript(src) {
	return new Promise((resolve, reject) => {
		const script = document.createElement('script')
		script.src = src
		script.onload = () => resolve(script)
		script.onerror = reject
		document.body.appendChild(script)
	})
}
loadScript('js/new-1.js').then(script => {
	console.log(script)
	return loadScript('js/new-2.js')
}).then(script => {
	console.log(script)
})

Promise.resolve()

Promise.resolve(value) 方法返回一个以给定值解析后的 Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是 thenable(即带有 "then" 方法),返回的 promise 会“跟随”这个 thenable 的对象,采用它的最终状态;否则返回的 promise 将以此值完成。此函数将类 promise 对象的多层嵌套展平。[MDN 文档](Promise.resolve() - JavaScript | MDN (mozilla.org))

js
// 缓存数据
function query(name) {
	const cache = queery.cache || (query.cache = new Map())
	if(cache.has(name)) {
		return Promise.reolve(cache.get(name))
	} 
	return ajax(`xxx?name=${name}`)
			.then(res => {
				cache.set(name, res)
				return res
			})
}
query('john').then(user => {
	console.log(user)
})
setTimeout(() => {
	query('john').then(user => {
		console.log(user)
	})
}, 2000)

// Promise.resolve 结合 thenable
let obj = {
	then(resolve, reject) {
		console.log('in obj')
		resolve('ok')
	}
}
Promise.resolve(obj).then(v => {
	console.log(v, 'over')
})

Promise.reject() 的使用

Promise.reject() 方法返回一个带有拒绝原因的  Promise  对象。

js
Promise.reject('fail').then(v => {
	console.log(v, 'ok')
}).catch(err => {
	console.log(err)
})

Promise.all() 的使用

Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个Promise实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个Promise的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息。 [MDN 文档 Promise.all()](Promise.all() - JavaScript | MDN (mozilla.org))

js
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// Array [3, 42, "foo"]

// ---------------------------- //
function getUsers(names) {
	let promises = names.map(name => {
		return ajax(`xxx?name=${name}`)
	})
	return Promise.all(promises)
}
getUsers(['john', 'jack']).then(users => {
	console.log(users)
}).catch(err => {
	console.log(err)
})

Promise.allSettled() (ES11/ES2020)

Promise.allSettled() 方法返回一个在所有给定的 promise 都已经 fulfilledrejected 后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。 当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise 的结果时,通常使用这个。 相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject 时立即结束。

js
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).then((results) => 
	results.forEach((result) => 
		console.log(result.status)
	)
);
// "fulfilled"
// "rejected"

Promise.race()

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。 Promise.race() - JavaScript | MDN (mozilla.org)

js
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
  console.log(value);
});
// two

使用 map() 和 reduce() 实现 Promise 队列

js
function queue(num) {
	let promise = Promise.resolve()
	num.map(v => {
		promise = promise.then(_ => {
			return new Promise(resolve => {
				setTimeout(() => {
					console.log(v)
					resolve()
				}, 1000)
			})
		})
	})
}
queue([1, 2, 3]) // 每隔一秒打印出 1 2 3
// 另外一个写法
function queue(num) {
	let promise = Promise.resolve()
	num.map(v => {
		promise = promise.then(_ => {
			return v()
		})
	})
}
function p1() {
	return new Promise(resolve => {
		setTimeout(() => {
			console.log('p1')
			resolve()
		}, 1000)
	})
}
function p2() {
	return new Promise(resolve => {
		setTimeout(() => {
			console.log('p2')
			resolve()
		}, 1000)
	})
}
queue([p1, p2]) // 依次打印出 p1 p2
// ------------------------------------------ //
// 使用 reduce 实现队列
function queue(num) {
	num.reduce((promise, n) => {
		return promise.then(_ => {
			return new Promise(resolve => {
				setTimeout(() => {
					console.log(n)
					resolve()
				}, 1000)
			})
		})
	}, Promise.resolve())
}
queue([1, 2, 3]) // 每隔一秒打印出 1 2 3

async 函数和关键词 await (ES8/ES2017)

返回值是一个 Promise,这个 promise 要么会通过一个由 async 函数返回的值被解决,要么会通过一个从 async 函数中抛出的(或其中没有被捕获到的)异常被拒绝。 async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。 async/await 的目的为了简化使用基于 promise 的 API 时所需的语法。async/await 的行为就好像搭配使用了生成器和 promise。 async 函数 - JavaScript | MDN (mozilla.org)

await 操作符用于等待一个Promise 对象。它只能在异步函数 async function 中使用。 返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。 await - JavaScript | MDN (mozilla.org)

js
async function foo() {
	return 'foo' // Promise.resolve('foo')
}

写一个 sleep 函数

js
async function sleep(delay = 1000) {
	return new Promise(resolve => {
		setTimeout(() => {
			resolve()
		}, delay)
	})
}
async function show(users = []) {
	for (const user of users) {
		await sleep(1200)
		console.log(user)
	}
}
show(['hello', 'world'])

class 与 await 结合

js
class User {
	constructor(name) {
		this.name = name
	}
	then(resolve, reject) {
		resolve({name: this.name, age: 18})
	}
}
async function getName() {
	let user = await new User('hello')
	console.log('after new User :', user)
}
getName()

async 和 await 的错误处理

js
async function run() {
	throw new Error('fail')
}
run().catch(err => {
	console.log(err)
})

async function getUser(name) {
	try {
		let user = ajax(`xxx?name=${name}`)
		return user
	} catch(err) {
		console.log(err)
	}
}
getUser('xi')
.then(user => {
	console.log(user)
})
.catch(err => {
	console.log(err)
})

await 并行执行技巧

js
function p1() {
	return new Promise(resolve => {
		setTimeout(() => {
			resolve('p1')
		}, 1000)
	})
}
function p2() {
	return new Promise(resolve => {
		setTimeout(() => {
			resolve('p2')
		}, 1000)
	})
}
async function run() {
	let res1 = p1()
	let res2 = p2()
	let resV1 = await res1 // 这里相当于 then 的写法
	let resV2 = await res2
	console.log(resV1, resV2)

	// 换一个写法
	let res = Promise.all([p1, p2])
	console.log(res)
}

Released under the MIT License.