形象解密Promise、Generator 函数、Async 函数三者之间的关系(上)

作者案:

最近被这三个兄弟搞得是晕头转向,然后决定花一番功夫比较深入的去了解一番,本着做为一名资深前端老油条的善良初心,决定还是布道,对还是布道一下,不但是加深自己对知识的理解,更是为了让后来的萌新们少走弯路,不用大费周折的去查阅各种资料,如果有幸看到这篇文章,那我恭喜您,这一篇文章我敢保定你能搞定这三者之间的关系~~~

Promise

产生的原因

解决原来ES5中的回调地狱

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise的三种状态

  • pending 进行中[待定]
  • fulfilled 已成功[实现]
  • rejected 已失败[被否决]

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

  • 当promise状态发生改变,就会触发then()里的响应函数处理后续步骤;
  • promise状态一经改变,不会再变。
  • Promise对象的状态改变,只有两种可能:
    • 从pending变为fulfilled
    • 从pending变为rejected。

这两种情况只要发生,状态就凝固了,不会再变了。

Promise的用法

Promise对象是一个构造函数,用来生成Promise实例,用法很简单,new Promise(Fun) 既然Promise是个构造函数,接受一个带有resolve和reject两个参数的函数作为参数。既然是构造函数,肯定会想到原型(Prototype)方法和实例方法

let fun = function(resolve, reject) {
    if (/* 异步操作成功 */) {
      resolve(value);
    } else {
      reject(error);
    }
  }
  const promise = new Promise(fun);
复制代码

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

怎么拿到promise的值呢

promise.then(function(){})
         .catch(function(){})
复制代码

这样的话是不是很清晰明了,现在你可能还很迷惑,Promise到底解决了啥,他的优势在哪里呢,客官姑且往下看: 我们拿一个很经典的ajax请求来说,原来我们是怎么做呢:

$.ajax({
    url: 'url',
    dataType: 'json', //数据类型
    type: 'GET',
    timeout: 2000, //2s后超时
    success: function() {
      // 根据外层请求的结果,然后再次发送请求,循环嵌套
      $.ajax({
        url: 'url',
        success: function() {}
      })
    },
    error: function() {
      if(textStatus==='timeout'){
        alert('請求超時');
      }
    }
  })
复制代码

看到上面代码中的例子是不是有点熟悉,很常见,网上说这叫什么 --- 回调地狱 , 一般情况我们一次性调用API就可以完成请求。 有些情况需要多次调用服务器API,就会形成一个链式调用,比如为了完成一个功能,我们需要调用API1、API2、API3,依次按照顺序进行调用,这个时候就会出现回调地狱的问题

注: 个人感觉倒是觉得还好,最多代码楼层太多,看起来不优雅而已。实际情况中我好像也没用碰到太多层的情况,不过好像还是很容易理解的哦

总有人说不优雅,那优雅的写法是什么样的呢,比如:

var _ajax = $.ajax('http://...');
  _ajax.ifSuccess(success)
       .ifFail(fail);
复制代码

如果这样写的话就简单明了,经过万千程序员的不懈努力,ES6终于接收并原生实现了改方法,接下来咱们看看Promise这厮是怎么处理这个逻辑的,我拿一个登陆逻辑为例:

// 函数1:判断用户登陆成功
    let userLogin = (res) => {
         var user = new promise((resolve, reject) => {
          $.ajax({
            url: 'url1',
            type: 'get',
            data: { userName: 'Virgo', userPwd: '123456' },
            success: res => {
              resolve(res);            // 请求成功则转成Promise对象并判断为resolve状态
            },
            error: err => {
              reject(err.status);      // 请求失败则转成Promise对象并判断为reject状态
            }
        })
        return user;
    }

    // 函数2:取出对应登录用户的信息
    let getUserInfo = (res)=> {
        var userInfo = new Promise((resolve, reject) => {
          $.ajax({
            url: 'url2',
            type: 'get',
            data: { status: 'success to finish getUserInfo!' },
            success: res => {
              resolve(res);            // 请求成功则转成Promise对象并判断为resolve状态
            },
            error: err => {
              reject(err.status);      // 请求失败则转成Promise对象并判断为reject状态
            }
        })
    }

    // 数据获取
    userLogin().then((v1) => {
      console.log(v1)
      return getUserInfo('fun2')
    }).then((v2)=>{
      console.log(v2)
    }).catch(()=> {
      // 捕获到错误
    })
复制代码

看到这里,大家是否看的明白,我们稍微改造了下fun1、fun2、fun3.让他们都返回的了Promise对象,之后调用then方法,并传入回调,then中的回调方法,就是resolve。Promise让所有的回调全部用了then进行传入,这种链式写法,看上去会清晰些。

注:借用别人一句话, 我看来看去还是觉得回调式更舒服,我仿佛是个智障

总结以下Promise的优势

  • promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
  • 并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据
  • 代码风格,容易理解,便于维护
  • 多个异步等待合并便于解决
  • 避免回调地域,这个看你怎么理解了啊

具体的关于Promise的详细用法,由于本篇文章着重讲的是Promise和Generate、Async/wait 的区别,我在这里就不着重展开,大家要Promise的细节,请移步: 阮一峰-ECMAScript 6 入门之Promise 廖雪峰的官方网站-Promise

Generator

像写同步代码一样写异步 --- 这个函数就像一个忍者,忠诚而可靠,行动全靠指令,没有命令,绝不越雷池半步

Generator介绍

Generator 的中文名称是生成器,它是ECMAScript6中提供的新特性。在过去,封装一段运算逻辑的单元是函数。函数只存在“没有被调用”或者“被调用”的情况,不存在一个函数被执行之后还能暂停的情况,而Generator的出现让这种情况成为可能。

通过function*来定义的函数称之为“生成器函数”(generator function),它的特点是可以中断函数的执行,每次执行yield语句之后,函数即暂停执行,直到调用返回的生成器对象的next()函数它才会继续执行。也就是说Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数返回一个遍历器对象(一个指向内部状态的指针对象),调用遍历器对象的next方法,使得指针移向下一个状态。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

yield

真正让Generator具有价值的是yield关键字,这个yield关键字让 Generator内部的逻辑能够切割成多个部分 还是那句话,你们网上能都搜到的,我这里就不再啰嗦了,本篇文章着重介绍三者之间的关系,请往下看:

let compute = function* (a, b) {
  let sum = a + b;
  yield console.log(sum);
  let c = a - b;
  yield console.log(c);
  let d = a * b;
  yield console.log(d);
  return;
};
复制代码

上面的代码中,被关键字 yield 分割成3个模块,无限细分,可以这样理解,

把computed函数看作一辆火车,每个yield就是中间的每一做火车站,铁路局按照每条线路编制好每条线路的运行线路,按照事先预计的线路有规则有秩序的运行,每到一个火车站要做一系列的事情,我把这个事情形象的展示一下:

let G1233 = function* (a, b) {
  let 上海 = function() {
    对列车全检、将本列车将要销售的鸡腿哈根达斯装车、水箱注水等等
  };
  yield 上海段情况;
  let 阜阳 = function() {
    因为要出上海铁路局管辖范围则需要更换火车头、再次注水、办理补票、扔垃圾、补充本地土特产、对阜阳美景进行宣传等等
  };
  yield 阜阳段情况;
  let 西安 = function() {
    列车到站进行列车清理、列车检修、驾驶员换班、开始返程等等
  };
  yield 西安段情况;
  return;
};
复制代码

return

看上面的代码,每个Generate函数最终都会有一个return,什么意思呢,专业术语叫做,每个yield收到控制权后,必须要用return交出控制权,不然后续的next就拿不到数据

next

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

我们还拿上面的高铁G1233举例,我们想拿到上海段情况,怎么办呢,很简单G1233.next()

G1233.next()  // 拿到上海段列车运行工作情况
G1233.next()  // 拿到阜阳段列车运行工作情况
G1233.next()  // 拿到西安段列车运行工作情况
G1233.next()  // {value: undefined, done: true}  铁路局就安排了这三段工作,该给的已经给了,交差!!!完成了[done: true]
复制代码

for ... of

我擦,牛逼牛逼,一条线路也不止3站吧,获取20站总不可能一直next()下去吧,:smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning::smiley::grinning: 哈哈,对了,可以迭代的,generator和Promise一样也是个对象,对象就可以迭代,for ... of嘛

for (var x of G1233()) {
    console.log(x); // 依次输出上海的、阜阳的、西安的
}
复制代码

码字千千万,写到这里,一天也就过去了,容我暂时止笔,且听我下次娓娓道来~~~

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章