async与await

async/await是es6中Promise的一个应用,让以同步方式写的代码能够异步执行。

async

async关键字用于声明异步函数,这个关键字可以用在函数声明、函数表达式、箭头函数和方法上。
如果使用async来修饰一个普通的JavaScript函数,那其实和普通函数没什么区别。但如果函数内使用了return关键字返回了值,这个值会被 Promise.resolve()包装成一个Promise对象。

async function foo () {
	console.log(1)
	return 3  //相当于 return Promise.resolve(3)
}
foo().then(console.log)
console.log(2)
//1
//2
//3

异步函数的返回值期待(实际上并不要求)一个实现thenable接口的对象,也可以是常规的值(如上面的例子,会被当成已解决(resolve)的Promise)。如果返回值是一个实现thenable接口的对象,则可以被then ()的程序“解包”:

async function baz() {
	const thenable = {
		then(callback) { callback('baz') }
	}
	return thenable
}
baz().then(console.log)
//baz

在异步函数中抛出的错误会返回一个拒绝的Promise:

async function foo() {
	console.log(1)
	throw 3
}
foo().catch(console.log)
console.log(2)
//1
//2
//3

而拒绝Promise的错误不会被异步函数捕获, 注意与上个例子的区别:

async function foo() {
	console.log(1)
	Promise.reject(3)
}
foo().catch(console.log)
console.log(2)
//1
//2
//Uncatch (in promise): 3

await

因为异步函数主要针对不会马上完成的任务,所以需要一种暂停和回复执行的能力。使用await关键字可以暂停异步函数代码的执行,等待Promise解决。

async function foo(){
	let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3))
	console.log(await p)
}
foo()
//3
//如果没有await的话,输出的是Promise {  },也就是状态还未改变的时候。

await关键字会暂停执行异步函数后面的代码,让出JavaScript运行时的执行线程,这个行为和Generator中的yield关键字是一样的。await同样是尝试“解包”对象的值,然后将这个值传给表达式,再异步恢复异步函数的执行。

await关键字期待(实际上并不要求)一个实现thenable接口的对象,也可以是常规的值。如果返回值是一个实现thenable接口的对象,则可以被then ()的程序“解包”:

async function foo() {
	console.log(await 'foo')
} 
foo()
//foo

async function baz() {
	const thenable = {
		then(callback) { callback('baz') }
	}
	console.log(await thenable)
}
baz()
//baz

await会抛出错误的同步操作,会返回拒绝的Promise

async function foo(){
	console.log(1)
	await (() => { throw 3 })()
}
foo().catch(console.log)
console.log(2)
//1
//2
//3

而对于拒绝的Promise使用await,会释放错误值:

async function foo() {
	console.log(1)
	await Promise.reject(3) 
	console.log(4) //不会被执行
}
foo().catch(console.log)
console.log(2)
//1
//2
//3

async/await的重点在于await,要完全理解await关键字,必须知道它并非等待一个值可用那么简单。JavaScript在运行时碰到await,会记录在哪里暂停执行。等到await右边的值可用了,JavaScript运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。
所以,就算await后面跟着一个立即可用的值,函数的其余部分也会被 异步 求值:

async function foo() {
	console.log(2)
	await null
	console.log(4)
}
console.log(1)
foo()
console.log(3)
//1
//2
//3
//4

如果await后跟着一个Promise,则问题会比较复杂一些。此时,为了执行异步函数,实际上会有两任务呗添加到消息队列并异步求值:

async function foo(){
	console.log(2)
	console.log(await Promise.resolve(8))//第一次执行到这里,将Promise.resolve(8)添加到消息队列
	//中异步求值, 第二次执行到这里Promise.resolve(8)得到了数字8, 而await 会将8提交给消息队列,
	//第三次来到这里才从消息队列得到8
	console.log(9)
}
async function bar(){
	console.log(4)
	console.log(await 6)
	console.log(7)
}
console.log(1)
foo()
console.log(3)
bar()
console.log(5)

//1
//2
//3
//4
//5
//6
//7
//8

实现sleep()

async function sleep(delay) {
  return new Promise(resolve => setTimeout(resolve, delay))
}
async function foo() {
  const t0 = Date.now()
  await sleep(1500)
  console.log(Date.now() - t0)
}
foo()
//1512

利用平行执行加速

假设这里有3个promise对象,他们之间没有任何依赖关系

await promise(1)
await promise(2)
await promise(3)

那么这三个语句将会被顺序执行,但由于他们之间并没有依赖关系,那么哪个先执行完成是无关紧要的,而顺序等待则浪费了可以并行执行的时间。所以可以一次性初始化所有promise,然后再分别等待结果:

const promises = new Array(3).fill(null).map((_, i) => promise(i))
for(const p of promises){
	await p
}

这样哪个promise先完成是不确定的,但await的顺序却是确定的,可以保证按顺序获取到promise的结果。

以上内容均参考自《Javascript高级程序设计》第4版, 当作学习的一个笔记。

你可能感兴趣的:(javascript,前端)