异步处理

阶段一:回调函数

当异步任务完成,调用对应回调函数(一般有两个:成功回调和失败回调),并将数据或错误以函数参数传入

阶段二:Promise

ES6 新增的 API,目的是为了解决异步处理中回调函数方式的一些问题:

  1. 回调地狱:回调函数层层嵌套(回调套回调),代码难以阅读和维护
  2. 控制反转:如不能保证回调函数被正确调用(一个回调可能被调用多次、成功回调和失败回调都被调用)
    • 规则 VS. 规范

Promise 如何解决?

  1. .then(...).then(...) 打平,无需嵌套
  2. Promise 的状态只能够改变一次(pending → resolvedpending → rejected

Promise 可以完全代替回调函数?

  • 不能。需要多次调用的回调函数、事件的回调函数(写成 Promise 很奇怪)

阶段三:Generator 函数

生成器函数可以退出,并在稍后重新进入,其上下文(变量绑定)会在重新进入时保存

function* 声明创建一个 GeneratorFunction 对象。每次调用生成器函数时,它都会返回一个新的 Generator 对象,该对象符合迭代器协议。当迭代器的 next() 方法被调用时,生成器函数的主体会被执行,直到遇到第一个 yield 表达式,该表达式指定了迭代器要返回的值,或者用 yield* 委托给另一个生成器函数。next() 方法返回一个对象,其 value 属性包含了 yield 表达式的值,done 属性是布尔类型,表示生成器是否已经返回了最后一个值。如果 next() 方法带有参数,那么它会恢复生成器函数的执行,并用参数替换暂停执行的 yield 表达式

其本质就是一个状态机

阶段四:asyncawait

Promise + GeneratorFunction 的语法糖,可以写看起来像同步的代码而完成异步编程

Promise

知识点

  • new Promise(executor) 中的 executor 函数体的代码是同步执行的
  • .then(onfulfilled, onrejected) 中的 onfulfilledonrejected 回调函数是放入微任务队列,放入时机:
    • 调用 .thenPromise 还是 pending:当 Promise 完成时(resolvereject)放入
    • 调用 .thenPromise 状态已改变:立即放入
  • 每调用一个 .then 就产生一个新的 Promise 并作为返回值(.then 的链式调用),其状态取决于该 then 中回调函数的返回值:
    • 返回普通值:状态是成功(resolved),相当于:Promise.resolve(返回值)
    • 返回 Promise状态吸收
  • .catch 同理,它只是 .then 的语法糖
  • .finally 也会产生一个新的 Promise 并作为返回值,其状态和当前 Promise 状态一致(没有状态吸收)
    • 其参数回调函数 onFinally 没有参数,返回值也会被忽略
  • await xxx:xxx 同步执行,如果不是一个 Promise 则使用 Promise.resolve() 包裹
    • 当该 Promise 完成后把后面所有的执行语句推入到微任务队列
    • 如果没有后续语句则推入“函数完成”到微任务队列
  • 每调用一个 async 函数就产生一个新的 Promise 并作为返回值,其状态取决于该函数的返回值:
    • 返回普通值:状态是成功(resolved),相当于:Promise.resolve(返回值)
    • 返回 Promise状态吸收
  • async 函数具有传染性,消除传染性:
    • 调两次,第一次抛出 Promise,在该 Promise 中进行异步操作,在外部捕获该错误,Promise 完成后再次调用函数
    • React 的 <Suspense fallback={Loading}>child</Suspense> 就是如此实现的
    • https://www.bilibili.com/video/BV1hp4y1A71j/

状态吸收

Promise 状态吸收是指:

  • 一个 Promise 要与另一个 Promise 状态保持一致
  • 即:后者的状态决定了前者的状态,前者需要吸收后者的状态

以下三种情况下,涉及 Promise 的状态吸收:

// 情况 1: .then 的参数回调函数返回了一个 Promise(.catch 一样)
const p2 = p.then(() => p1)
// p2 要吸收 p1 的状态
 
// 情况 2: async 函数返回了一个 Promise
const p4 = await asyncFunc()
async function asyncFunc() {
  return p3
}
// p4 要吸收 p3 的状态
 
// 情况 3: resolve 接收了一个 Promise(reject 一样)
const p6 = new Promise((resolve, reject) => {
  resolve(p5)
})
// p6 要吸收 p5 的状态
 
// 注意:Promise.resolve() 接收 Promise 参数不会状态吸收
const p8 = Promise.resolve(p7)
// 这里的 p8 === p7
 
// 注意:finally() 返回的新 Promise p10 与 p9 状态一致,但没有状态吸收
const p10 = p9.finally()

Promise A+ 规范没有详细规定如何状态吸收,如何保持状态一致取决于 JS 引擎具体实现,下面说一下 Chrome 和 Node.js 的 V8 的实现方式

V8 将状态吸收分为两个步骤:

  1. 准备
  2. 吸收

每一个步骤都是放到微队列中运行

拿上面的情况 1 举例:

  • p1 完成后
  • 准备:将 p2 状态 = p1 状态 推入微队列,相当于 p1.then(() => p2 状态 = p1 状态)
  • 吸收:相当于从微队列拿出 p2 状态 = p1 状态 执行,这时 p2 完成了且状态与 p1 一致

题目

1

async function async1() {
	console.log(1);
	await async2();
	console.log(2);
}
 
async function async2() {
	console.log(3);
}
 
console.log(4);
 
setTimeout(function () {
	console.log(5);
}, 0)
 
async1();
 
new Promise(function (resolve) {
	console.log(6);
	resolve();
}).then(function () {
	console.log(7);
});
 
console.log(8);

2

console.log(1);
 
setTimeout(() => {
	console.log(2);
	Promise.resolve().then(() => {
		console.log(3);
	})
}, 0);
 
new Promise(function (resolve, reject) {
	console.log(4);
	setTimeout(function () {
		console.log(5);
		resolve(6);
	}, 0);
}).then((res) => {
	console.log(7);
	setTimeout(() => {
		console.log(res);
	}, 0);
})

3

const p = function() {
	return new Promise((resolve, reject) => {
		const p1 = new Promise((resolve, reject) => {
			setTimeout(() => {
				resolve(1);
			}, 0);
			resolve(2);
		});
	
		p1.then((res) => {
			console.log(res);
		})
	
		console.log(3);
		resolve(4);
	});
}
 
p().then((res) => {
	console.log(res);
});
console.log ('end');

4

const p = function() {
	return new Promise((resolve, reject) => {
		const p1 = new Promise((resolve, reject) => {
			setTimeout(() => {
				resolve(1);
			}, 0);
//			resolve(2);
		});
	
		p1.then((res) => {
			console.log(res);
		})
	
		console.log(3);
		resolve(4);
	});
}
 
p().then((res) => {
	console.log(res);
});
console. log ('end');

5

async function f1() {
  console.log(1)
  await f2()
  console.log(2)
}
 
f2 = async () => {
  await setTimeout(() => {
    Promise.resolve().then(() => {
	  console.log(3)
    })
    console.log(4)
  })
}
 
f3 = async () => {
  Promise.resolve().then(() => {
    console.log(6)
  })
}
 
f1()
console.log(7)
f3()

6

async function f1() {
  console.log(1)
  await f2()
  console.log(2)
}
 
f2 = async () => {
  await (async () => {
    await (() => {
      console.log(3)
    })()
    console.log(4)
  })()
}
 
f3 = async () => {
  Promise.resolve().then(() => {
    console.log(6)
  })
}
 
f1()
console.log(7)
f3()

7

const p1 = new Promise((resolve, reject) => {
  resolve()
})
 
const p2 = new Promise((resolve, reject) => {
  resolve(p1)
})
 
p2.then(() => {
  console.log('1')
})
  .then(() => {
    console.log('2')
  })
  .then(() => {
    console.log('3')
  })
 
p1.then(() => {
  console.log('4')
})
  .then(() => {
    console.log('5')
  })
  .then(() => {
    console.log('6')
  })

8

async function async1() {
  console.log(1)
  await async2()
  console.log('AAA')
}
 
async function async2() {
  return Promise.resolve(2)
}
 
async1()
 
Promise.resolve()
  .then(() => console.log(3))
  .then(() => console.log(4))
  .then(() => console.log(5))

9

async function async1() {
  console.log(1)
  await async2()
  console.log('AAA')
}
 
function async2() {
  return Promise.resolve(2)
}
 
async1()
 
Promise.resolve()
  .then(() => console.log(3))
  .then(() => console.log(4))
  .then(() => console.log(5))

10

Promise.resolve().then(() => {
  console.log(0)
  return Promise.resolve(4)
}).then(res => {
  console.log(res)
})
 
Promise.resolve().then(() => {
  console.log(1)
}).then(res => {
  console.log(2)
}).then(res => {
  console.log(3)
}).then(res => {
  console.log(5)
}).then(res => {
  console.log(6)
})