JavaScript异步代码处理方案

异步代码的问题

  1. 不得不说在早期的js中,处理异步代码是一个极具挑战性的问题,也是一个即为头疼的问题,不是说编写异步代码有什么难度,而是编写出维护性与阅读性并存的异步代码有难度,特别是asyncawait还没有出现的时代,经常在代码中出现一些回调地狱。所以今天我们就结合具体场景来看看异步代码的演进过程,了解历史才能更接近真相。

准备工作

  1. 首先很简单,我们就模拟实际开发中经常出现的场景,有的时候我们发送网络请求,需要拿到上一次网络请求的返回值,作为下一次发送网络请求的参数。我们将这个场景简化一下,定义一个Promise
    1. function request(url) {
    2. return new Promise((resolve, reject) => {
    3. resolve(url);
    4. });
    5. }
    6. //我们定义一个函数,返回一个Promise,Promise内部做的事情也很简单,直接将传进来的参数丢出去

第一种解决方案

  1. 结合实际开发场景,出现了下面的代码
  1. //1. 我们首先传入一个url
  2. //2. 然后拿到返回值后在拼接一个url继续发送网络请求
  3. //3. 拿到第二次网络请求的返回值后继续拼接url发送网络请求拿到返回值
  4. const result = request("http://studyvue.cn");
  5. result.then(res => {
  6. // console.log(res);
  7. request(res + "/coderwei").then(res => {
  8. // console.log(res);
  9. request(res + "/19").then(res => {
  10. console.log(res);
  11. });
  12. });
  13. });
  1. 于是我们的代码演变成这个样子,也不是不能看,但是不得不说阅读性极差,作为一个合格的前端开发工程师,并不能允许自己的代码频繁出现这样的结构,如果依赖上一次网络请求的次数更多,那么最后的代码将会是惨不忍睹。不得庆幸的是人是会慢慢进步的,于是在时间的推移下,上面的代码进行的进化。

第二种解决方案

  1. const result = request("http://studyvue.cn");
  2. result
  3. .then(res => {
  4. return request(res + "/coderwei");
  5. })
  6. .then(res => {
  7. return request(res + "/19");
  8. })
  9. .then(res => {
  10. console.log(res);
  11. });
  1. 首先我们需要知道Promise的返回值也是一个Promise,当我们把一个字符串作为Promise的参数传递进去的时候,会自动调用Promise.resolve方法,当然传递其他类型也会有不同的处理方式,比如说传递一个函数,会根据实际情况调用resolve或者是reject方法,这篇文章也不是讲Promise的,就不多赘述了。看向我们上面的代码,将一个Promise传递一个字符串后返回出去,我们就可以在后面继续调用then方法,这样就形成了一个链式调用,现在代码看起来阅读性就比第一种方案好多了

第三种方案

  1. 第三种方案就有点难以理解,我们需要借助Promise+生成器来完成,先写个简易版的,来看下代码的执行顺序
    ```javascript //我们先定义一个生成器 function* foo() { yield request(“http://studyvue.cn“); }

const result = foo(); result.next().value.then(res => { console.log(res); });

  1. - 首先我们先定义一个**生成器**
  2. - 然后调用这个**生成器**,拿到返回值,返回值是一个**迭代器**,不知道**迭代器**的可以看我上一篇文章,我个人觉得还是讲的很全面的
  3. - 然后调用next方法拿到一个对象,这个对面长这个样子:{ value: Promise { '[http://studyvue.cn](http://studyvue.cn)' }, done: false }
  4. - 我们可以看到我们需要的东西在value属性上,并且它的值是一个Promise,于是我们拿到value的值,并且调用then方法就能拿到这次网络请求的返回值了
  5. 2. 然后在考虑下传递参数的问题,因为我们不是拿到返回值就完事了,如果是这样何必绕那么一大圈。所以我们还需要知道生成器是可以传递参数的,简单来说就是第二次调用next传递的参数能作为生成器的第一次yield 的返回值拿到,看代码 <br /> 这种方式看起来还是一种回调地狱,但是问题不大,我们可以看看具体处理逻辑,其实都差不多的,都是调用next方法传递参数,然后拿到value属性的值调用then方法而已,所以这里我们可以写成一个递归,当然这种代码不会写也没关系,npm仓库里就有对应的包帮我们执行我们写的生成器(co),递归的代码:
  6. ```javascript
  7. function* foo() {
  8. const result1 = yield request("http://studyvue.cn");
  9. const result2 = yield request(result1);
  10. yield request(result2);
  11. }
  12. const result = foo();
  13. result.next().value.then(res => {
  14. result.next(res + "/coderwei").value.then(res => {
  15. result.next(res + "/19").value.then(res => {
  16. console.log(res);
  17. });
  18. });
  19. });
  1. // 3. 改进 ---->递归
  2. function* foo() {
  3. const result1 = yield request("http://studyvue.cn");
  4. const result2 = yield request(result1 + "/coderwei");
  5. const result3 = yield request(result2 + "/19");
  6. console.log(result3);
  7. }
  8. function execGenerator(generatorFn) {
  9. const generator = generatorFn();
  10. function exec(res) {
  11. const result = generator.next(res);
  12. if (result.done) {
  13. return result.value;
  14. }
  15. result.value.then(res => {
  16. exec(res);
  17. });
  18. }
  19. exec();
  20. }
  21. execGenerator(foo); //http://studyvue.cn/coderwei/19

第四种方案

  1. 第三种方案的递归写起来还是有点麻烦的,所以我们就可以借助第三方库。这个时候就体现了一门语言有一个完善的生态和社区是多么重要。npm的仓库有一个叫co的包在处理这种代码时很出名的,他也是大神TJ的作品,同时他也是express/koa/n/commander的作者,在asyncawait还没出来之前,co在处理这种代码几乎是必备的。
  1. 首先要使用第三方包肯定要先安装

    1. npm install co
  2. 然后导入

    1. const co = require('co')
  3. 进行使用
    这就完事了,就这么简单粗暴,co本质上就是我们在上面编写的递归函数,在node_modules文件夹中找到co,可以看到源码也就200多行,也容易看懂,而且大部分都是在做边界判断(edge case) ```javascript function* foo() { const result1 = yield request(“http://studyvue.cn“); const result2 = yield request(result1 + “/coderwei”); const result3 = yield request(result2 + “/19”); console.log(result3); }

co(foo); // http://studyvue.cn/coderwei/19

  1. <a name="02f1d8ad"></a>
  2. ### 第五种方案(最终方案)
  3. 学习了那么多种方案,现在我们终于能引出最终的方案,也就是ES8(ES2017)推出的新特性:async/await
  4. ```javascript
  5. async function foo() {
  6. const result1 = await request("http://studyvue.cn");
  7. const result2 = await request(result1 + "/coderwei");
  8. const result3 = await request(result2 + "/19");
  9. console.log(result3);
  10. }
  11. foo(); // http://studyvue.cn/coderwei/19
  1. 是不是很神奇,只是将*删掉,然后在函数前面加上async,然后将所有的yield换成await,结果是一模一样的。虽然这两个关键字是ES8推出的,但是目前的开发中我们都会使用babel对代码进行一个转换,所以开发的时候也可以大胆地使用。