异步处理

阶段一:回调函数

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

阶段二:Promise

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

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

Promise 如何解决?

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

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

不能!

  • 需要多次调用的回调函数、事件的回调函数
  • Promise 只能修改一次状态,无法实现多次回调

阶段三:Generator 函数

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

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

其本质就是一个状态机,详见:Generator 函数

阶段四:asyncawait

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

示例:

function* fetchUser() {
  const data = yield fetch('/api/user');
  console.log(data);
}
// 手动执行(实际中常用 co 库或 async/await)
const g = fetchUser();
g.next().value.then(data => g.next(data));

Promise

function loadScript(src) {
    // 初始状态:pending,数据:undefined
    // 状态改变后无法逆转
    return new Promise((resolve, reject) => {
        const script = document.createElement('script')
        script.src = src
        script.onload = () = resolve(src)  // 状态:fulfilled,数据:result
        script.onerror = err => reject(err) // 状态:rejected,数据:error
        document.head.append(script)
    })
}
 
loadScript('./1.js')
    .then(() => loadScript('./2.js'))
    .then(() => loadScript('./3.js'))
    .catch(err => console.log(err))

APIs

  • Promise.prototype.then(onFulfilled, [onRejected]): Promise
    • then 的链式调用:前一个 then 的返回值决定下一个 then 函数的参数值
  • Promise.prototype.catch(onError): Promise
    • 错误穿透
    • then 的语法糖
  • 非 Promise → Promese
    • Promise.resolve(1): Promise
    • Promise.reject(new Error('unknown error')): Promise
  • Promise.all(PromiseArray): Promise
    • 同时执行多个 Promise 实例(并行),结果都要
    • PromiseArray:包含多个 Promise 实例或普通数据的可迭代对象
    • 即使一个出错了,其他的 Promise 也会继续执行,无法中断
    • 有一个 rejected,整体结果 rejected
  • Promise.race(PromiseArray): Promise
    • 同时执行多个 Promise 实例(并行),竞速:只要一个结果(谁快要谁)
    • PromiseArray:包含多个 Promise 实例或普通数据的可迭代对象
    • 即使一个出错或完成了,其他的 Promise 也会继续执行,无法中断
// Promise.all
const p1 = Promise.resolve(1)
const p2 = 2
const p3 = Promise.resolve(3)
Promise.all([p1, p2, p3]).then(result => {
    console.log(result) // [1, 2, 3]
})
 
// Promise.race
const p1 = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(1), 1000)
    })
}
const p2 = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(2), 0)
    })
}
 
Promise.race([p1(), p2()]).then(result => {
    console.log(result) // 2
)

知识点

  • 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)
})