简述 JS 异步发展不同阶段
简述 JS 异步发展不同阶段
下面用顺序读取三个文件为例子
Callback
node
早期API
中基本都是采用回调函数的方式。
如果要顺序读取三个文件,这里就会有一个历史经典的问题:回调地区(callback hell
),这里明显的缺陷就是:
- 代码层级嵌套过深,看着就恶心;
- 里外层代码之间强耦合,非常不宜维护,修改其中一层代码,可能会影响到别的很多层都得改;
const fs = require("fs");fs.readFile(demo1TxtPath, { encoding: "utf-8" }, (err, data) => { try { if (err) throw err; console.log(data); fs.readFile(demo2TxtPath, { encoding: "utf-8" }, (err, data) => { try { if (err) throw err; console.log(data); fs.readFile(demo3TxtPath, { encoding: "utf-8" }, (err, data) => { try { if (err) throw err; console.log(data); fs.readFile(); } catch (error) {} }); } catch (error) {} }); } catch (error) {}});
因此,Promise
横空出世,它的出现很大一部分原因,就是为了解决控制异步函数顺序执行中使用 callback
所带来的回调地狱的问题。
Promise
Promise
是异步编程的一种解决方案,比传统的解决方案callback(回调函数)
更合理和更强大。它最早是由社区提出和实现,
ES6
将其写进了语言标准,统一了用法,原生提供了Promise
对象。
const fsPromise = require("fs/promises");fsPromise .readFile(demo1TxtPath, { encoding: "utf-8" }) .then((data) => { console.log(data); return fsPromise.readFile(demo2TxtPath, { encoding: "utf-8" }); }) .then((data) => { console.log(data); return fsPromise.readFile(demo3TxtPath, { encoding: "utf-8" }); }) .then((data) => console.log(data));
这里发现,虽然Promise + then
解决了回调地狱的问题,但是,不同的异步操作之间多了很多冗余代码,看着也很难受。
Generator + yield
Generator 函数是
ES6
提供的一种异步编程解决方案,语法行为与传统函数完全不同。
function* generatorReadFile() { try { // 每一个 yield 表达式的后面都是一个运行结果 Promise 对象的表达式 const data1 = yield fsPromise.readFile(demo1TxtPath, { encoding: "utf-8" }); console.log("data1: ", data1); const data2 = yield fsPromise.readFile(demo2TxtPath, { encoding: "utf-8" }); console.log("data2: ", data2); const data3 = yield fsPromise.readFile(demo3TxtPath, { encoding: "utf-8" }); console.log("data3: ", data3); } catch (error) { console.log("e", error); }}// 自动执行(yield 后面必须是 Promise)function runByPromise(generator) { const g = generator(); function cb(data) { const { value, done } = g.next(data); if (done) return; value.then(cb); } cb();}runByPromise(generatorReadFile);
通过generator + yield + promise + runByPromise(自动执行)
也完成了异步操作顺序执行的效果。
虽说后面的自动执行可以封装起来,在用的地方直接用就行,但是强依赖于自动执行。
并且function* fnName
的 Generator 的写法总感觉有点不太自由。
async + await
ES2017
标准引入了async
函数,使得异步操作变得更加方便。
async
函数是什么?一句话,它就是 Generator 函数的语法糖。
async function asyncReadFile() { try { const data1 = await fsPromise.readFile(demo1TxtPath, { encoding: "utf-8" }); console.log("data1: ", data1); const data2 = await fsPromise.readFile(demo1TxtPath, { encoding: "utf-8" }); console.log("data2: ", data2); const data3 = await fsPromise.readFile(demo1TxtPath, { encoding: "utf-8" }); console.log("data3: ", data3); } catch (error) { console.log("e", error); }}
相比于generator + yield + promise + runByPromise(自动执行)
,这里不需要 自动执行 这一步,这里其实也能看出,async await
其实就是对自动执行进行了封装。
这是目前异步操作的终极解决方案。
async, await和 Generator 的关系
async, await
就是Generator + yield
的语法糖。
async
函数的实现原理,就是将 Generator 函数和 Generator 自动执行,包装在一个函数里。
async function fn(args) { // 代码 a b c}
// 等同于function coAsync() {...}function* gen() { // 代码 a b c}
function fn(args) { return coAsync(gen)}
所有的async
函数都可以写成上面的第二种形式,其中的coAsync
函数就是 自动执行器。
下面给出coAsync
函数的实现,也就是 Generator 的 自动执行器(自动执行到 Generator 终止)。
function coAsync(genF) { return new Promise(function (resolve, reject) { const gen = genF(); function step(nextF) { let next; try { // next: { value: cb, done: fasle / true } next = nextF(); } catch (e) { return reject(e); } // 如果 Generator 执行完之后, 函数终止, 且Promise状态由 pending 变化为 fulfilled if (next.done) { return resolve(next.value); }
// 对cb 进行拆分程三种情况 function cb(status = "v", val) { if (status === "v") { step((val) => gen.next(val)); } else if (status === "e") { step((e) => gen.throw(e)); } else { step(() => gen.next()); } } // 恢复 Generator的执行, 这里的Promise.resolve('xxx') 一定程度上可以等价为 new Promise((resolve, reject) => resolve('xxx')) // 关于这句话的疑惑, 可以参考 https://es6.ruanyifeng.com/#docs/promise#Promise-resolve
// 这里看得出来, 这个 next.value 目前只支持 Promise 类型的, 如果需要支持 thunk 类型的, 那么需要将 thunk 类型的 next.value 包装成 Promise 然后进行操作
// 如果next.value 就是一个 Promise, 那么这里等同于 next.value.then(xxx).catch(xxx) Promise.resolve(next.value) .then((v) => cb("v", v)) .catch((e) => cb("e", e)); } // 递归, 初始启动 step(() => gen.next()); });}
这里我们也看得出为什么async
函数的返回值是一个Promise
。
关于Promise
用法的疑惑请参考:https://es6.ruanyifeng.com/#docs/promise。