日常的学习笔记,包括 ES6、Promise、Node.js、Webpack、http 原理、Vue全家桶,后续可能还会继续更新 Typescript、Vue3 和 常见的面试题 等等。
同步阻塞与异步非阻塞
在了解异步的发展之前,我们要先清楚一个概念。那就是 同步异步与阻塞。
在执行代码的过程中,我们可能调用了某个方法。在调用方法时,如果方法没有执行完毕的话,后续的其他代码就无法向下执行,那么我当前的状态就是 阻塞态。 反之,便是 非阻塞态。
通过上述描述我们清楚了一个道理,那就是 阻塞与非阻塞指的是调用方的状态。
而针对于被调用方 ,延迟发送结果我们就可以称为 异步 ,而实时发送结果我们就可以称为 同步 。同步与异步指的是被调用方的状态。
所以在实际概念中,同步非阻塞是可能存在的,但异步阻塞是一定不存在的。
Promise
最早是为了解决异步的回调地狱问题,但是却并没有从根本上解决这个问题,所以我们可以考虑使用 Generator
。
Generator
参考文献 Generator函数的含义与用法
Generator
是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
基础语法
在普通函数中,我们可以利用 return 将函数的控制权交还给被调用的代码。
function read(n) {
return n + n;
}
let it = read(1); // 调用foo函数
函数在执行过程中,如果没有遇到return
语句(函数末尾如果没有return
,就是隐含的return undefined;
),控制权无法交回被调用的代码。
而我们的 Generator
的语法与函数相似,却又有一些不同。
Generator
由function*
定义(注意多出的*
号),并且,除了return
语句,还可以用yield
返回多次。
function* read() { // 生成器,他的执行结果叫做迭代器
console.log(1);
yield 1;
console.log(2);
yield 2;
console.log(3);
yield 3;
}
let it = read(); // 默认没有执行
// next方法触发回调
it.next();
定义的函数我们可以叫他 生成器,其执行结果我们可以叫做 迭代器。
实现原理
关于 Generator
的实现原理,我们可以从 babel官网 上进行查看。
通过打印,我们可以很清楚的发现一件事情。 那就是 Genetator
实现原理的核心方法就是 switch...case
。
我们每次在执行代码时,会在遇到 yield
时,将控制权交还给被调用的代码。等待下一次 .next()
方法,再执行后续的代码。
实际案例
假设我们现在有一个文件 a.txt,里面有 b.txt的路径(如 ./b.txt), 再将此内容作为参数,继续请求路径为 b.txt 的文件,得到结果 b。
实现上述需求,利用 Generator
可以解决我们在请求过程中的回调地狱问题。
const util = require('util');
const fs = require('fs');
let readFile = util.promisify(fs.readFile);
// 解决了回调地狱
function* read() {
let data = yield readFile('./a.txt', 'utf-8')
data = yield readFile(data, 'utf-8')
return data
}
// 依旧存在回调地狱的问题
let it = read()
let { value } = it.next()
value.then(data => {
let { value } = it.next(data)
value.then(data => {
let { value } = it.next(data)
console.log(value) // b
})
})
我们可以发现,发送请求时,我们利用 Generator
对异步请求进行了封装,使代码看起来更整洁了。
但是在调用的时候,依旧存在回调地狱的问题。
调用的回调地狱问题,我们可以使用 tj/co 库来进行处理。
这个库的实现原理,就是实现 async/await
的核心思路。
co插件
使用co库,可以直接将 Generator
函数的最终结果返回,而且会使代码看起来更简洁优雅。
npm install co
先安装插件库。
const co = require('co');
co(read()).then(data => {
console.log(data) // b
})
直接会将结果返回。
实现原理
function co(it) {
return new Promise((resolve, reject) => {
// 异步迭代,我们只能使用递归的方式
function next(data) {
let {
value,
done
} = it.next(data);
// 如果执行完毕,则将最终结果返回
if (done) {
resolve(value);
} else {
Promise.resolve(value).then(next, reject)
}
}
next();
})
}
async/await
还是参考上面那个例子。
const util = require('util');
const fs = require('fs');
const co = require('co');
let readFile = util.promisify(fs.readFile);
function* read() {
let data = yield readFile('./a.txt', 'utf-8')
data = yield readFile(data, 'utf-8')
return data
}
co(read()).then(data => {
console.log(data) // b
})
async/await
的实现思路就是 Generator
,将 Generator
中的 function * 替换成 async function ,把 yield 替换成 await 。
async function read() {
let data = await readFile('./a.txt', 'utf-8')
data = await readFile(data, 'utf-8')
return data
}
read().then(data => {
console.log(data) // b
})
这样我们就可以直接输出最终结果。
可以说 async/await
是目前解决回调地狱问题的最优方案。
本篇文章由莫小尚创作,文章中如有任何问题和纰漏,欢迎您的指正与交流。
您也可以关注我的 个人站点、博客园 和 掘金,我会在文章产出后同步上传到这些平台上。
最后感谢您的支持!