• JS异步解决方案


      前言

        异步最早的解决方案是回调函数,如ajax,事件的回调,setInterval/setTimeout中的回调。但是回调函数有回调地狱的问题;

        为了解决回调地狱的问题,社区提出了Promise解决方案,ES6将其写进了语言标准。Promise一定程度上解决了回调地狱的问题,但是Promise也存在一些问题,如错误不能被try catch,而且使用Promise的链式调用,其实并没有从根本上解决回调地狱的问题,只是换了一种写法。

        ES6中引入 Generator 函数,Generator是一种异步编程解决方案,Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权,Generator 函数可以看出是异步任务的容器,需要暂停的地方,都用yield语句注明。但是 Generator 使用起来较为复杂。

        ES7又提出了新的异步解决方案:async/await,async是 Generator 函数的语法糖,async/await 使得异步代码看起来像同步代码,异步编程发展的目标就是让异步逻辑的代码看起来像同步一样。

      同步和异步

      关于同步和异步的概念,这里给出阮一峰老师的文章参考。其中也不乏一些异步的解决办法例子,如《js异步编程的4种方法》《异步操作概述》

       异步解决方案

      回调函数

    1 // node读取文件
    2 fs.readFile('url', 'utf-8', function(err, data) {
    3   // code
    4 });

       使用的场景有:ajax请求,事件回调函数,Node API,定时器等。

       优点: 简单。 缺点: 异步回调嵌套会导致代码难以维护,并且不方便统一处理错误,不能 try,catch 和存在回调地狱问题。

    // 回调地狱问题,以读取A文本内容,再根据A文本内容读取B再根据B的内容读取C..为例
    fs.readFile('A', 'utf-8', function(err, data) {
        fs.readFile('B', 'utf-8', function(err, data) {
            fs.readFile('C', 'utf-8', function(err, data) {
                fs.readFile('D', 'utf-8', function(err, data) {
                    // code
                });
            });
        });
    });

        Promise

      Promise 一定程度上解决了回调地狱的问题,Promise 最早由社区提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。《ECMAScript 6 入门》

      我们来观察一下Promise是如何解决回调地狱问题的。

    function read(url) {
      return new Promise((resolve, reject) => {
           fs.readFile(url, 'utf-8', function(err, data) {
               if (err)  reject(err);
               resolve(data);
           });
      });
    }
    read('A').then(data => {
        return read('B');
    }).then(data => {
        return read('C');
    }).then(data => {
        return read('D');
    }).catch(err => {
    console.log(err);
    });

        promise的优点: 1.一旦状态改变,就不会再变,任何时候都可以得到这个结果。2.可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

             promise的缺点:1.无法取消 Promise。 2.当处于pending状态时,无法得知目前进展到哪一个阶段。 3.错误不能被 trycatch

             这里举一个在promise之前,读取A,B,C三个文件内容,都读取成功后,再输出最终的结果例子。依赖于发布订阅模式

    let pubsup = {
      arry: [],
      emit: function() {
        this.arry.forEach(fn => fn());
      },
      on: function(fn) {
         this.arry.push(fn);
      }
    };
    let data = [];
    pubsub.on(() => {
      if (data.length == 3) {
        console.log(data);
      }
    });
    fs.readFile('A', 'utf-8', function(err, data) {
      data.push(data);
      pubsup.emit();
    });
    fs.readFile('B', 'utf-8', function(err, data) {
      data.push(data);
      pubsup.emit();
    });
    fs.readFile('C', 'utf-8', function(err, data) {
      data.push(data);
      pubsup.emit();
    });

    顺便提一下朴灵的深入浅出nodejs里面介绍不同场景下异步获取数据进行操作的实践,非常值得看。

    Promise给我们提供了 Promise.all 的方法,对于这个需求,我们可以使用 Promise.all 来实现。

    /**
     * 将 fs.readFile 包装成promise接口
     */
    function read(url) {
      return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf-8', function(err, data) {
           if (err) reject(err);
           resolve(data);
        });
      });
    }
    /** 
     *  Promise.all 可以实现多个异步并行执行,同一时刻获取最终结果的问题
     */
    Promise.all([
      read(A),
      read(B),
      read(C)
    ]).then(data => {
      console.log(data);
    }).catch(err => console.log(err));

        Generator

            Generator 函数是 ES6 提供的一种异步编程解决方案,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。

            Generator 函数一般配合 yield 或 Promise 使用。Generator函数返回的是迭代器。对生成器和迭代器不了解的同学参考下阮一峰的ES6入门

    function* generator() {
      let a = yield 111;
      console.log(a);
      let b = yield 222;
      console.log(b);
      let c = yield 333;
      console.log(c);
      let d = yield 444;
      console.log(d);
    }
    let t = generator();
    // next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
    t.next(1); // 第一次调用next函数时,传递的参数无效
    t.next(2); // 输出2
    t.next(3); // 3
    t.next(4); // 4
    t.next(5); // 5

    仍然以上文的 readFile (先读取A文本内容,再根据A文本内容读取B再根据B的内容读取C)为例,使用 Generator + co库来实现:

    const fs = require('fs');
    const co = require('co');
    const bluebird = require('bluebird');
    const readFile = bluebird.promiseify(fs.readFile);
    function* read() {
      yield readFile('A', 'utf-8');
      yield readFile('B', 'utf-8');
      yield readFile('C', 'utf-8');
    }
    co(read()).then(data => {
      // code
    }).catch(err => {
     // code
    });

        await/async

          ES7中引入了 async/await 概念。async 其实是一个语法糖,它的实现就是将 Generator函数和自动执行器(co),包装在一个函数中。

          async/await 的优点是代码清晰,不用像 Promise 写很多 then 链,就可以处理回调地狱的问题。并且错误可以被try catch。

          仍然以上文的readFile (先读取A文本内容,再根据A文本内容读取B再根据B的内容读取C) 为例,使用 async/await 来实现:

    const fs = require('fs');
    const co = require('co');
    const bluebird = require('bluebird');
    const readFile = bluebird.promiseify(fs.readFile);
    async function read() {
      await readFile('A', 'utf-8');
      await readFile('B', 'utf-8');
      await readFile('C', 'utf-8');
    }
    co(read()).then(data => {
      // code
    }).catch(err => {
     // code
    });

      我们用async/await来实现同样的效果。

    function read(url) {
      return new Promise((resolve, reject) => {
          fs.readFile(url, 'utf-8', function(err, data) {
              if (err) reject(err);
              resolve(data);
          })
      });
    }
    async function readAsync() {
      const data = await Promise.all([
          read(A),  
          read(B),
          read(C)
      ]);
      return data;
    }
    readSync().then(data => {
      console.log(data);
    })

    所以JS的异步发展史,可以认为是从 callback -> promise -> generator -> async/await。async/await 使得异步代码看起来像同步代码,异步编程发展的目标就是让异步逻辑的代码看起来像同步一样

  • 相关阅读:
    ssh scp命令详解
    python模块与包
    python参数Sample Code
    python 多线程简介
    python virtualenv环境安装(ubuntu)
    python pip 代理设置
    mysql资源总结
    mysql索引原理及用法
    Oracle学习笔记:利用user_segments查表的大小
    Oracle学习笔记:dba_tables、all_tables、user_tables区别
  • 原文地址:https://www.cnblogs.com/cleaverlove/p/10962527.html
Copyright © 2020-2023  润新知