拥抱Generator,告别异步回调

本节学习目标

  • 实现一个生成器 :什么是迭代器?什么是生成器?generator概念,generator函数语法;

  • 异步处理 :了解异步编程;哪些解决办法;结合generator来解决异步;


Generator概念

Generator很像是一个函数,但是你可以暂停它的执行。你可以向它请求一个值,于是它为你提供了一个值,但是余下的函数不会自动向下执行直到你再次向它请求一个值。

什么是生成器?
function* quips(name) {
  yield "你好 " + name + "!";
  yield "你喜欢我吗";
  if (name.startsWith("yang")) {
    yield "我们五百年前是一家诶";
  }
  yield "再见!";
}
> var iter = quips("yang jiang");
  [object Generator]
> iter.next()
  { value: "你好 yang jiang!", done: false }
> iter.next()
  { value: "我们五百年前是一家诶", done: false }
> iter.next()
  { value: "再见!", done: false }
> iter.next()
  { value: undefined, done: true }

生成器调用看起来非常类似:quips("yang jiang")。但是,当你调用一个生成器时,它并非立即执行,而是返回一个已暂停的生成器对象(上述实例代码中的iter)。你可将这个生成器对象视为一次函数调用,只不过立即冻结了,它恰好在生成器函数的最顶端的第一行代码之前冻结了。

每当你调用生成器对象的.next()方法时,函数调用将其自身解冻并一直运行到下一个yield表达式,再次暂停。

如果用专业术语描述,每当生成器执行yields语句,生成器的堆栈结构(本地变量、参数、临时值、生成器内部当前的执行位置)被移出堆栈。然而,生成器对象保留了对这个堆栈结构的引用(备份),所以稍后调用.next()可以重新激活堆栈结构并且继续执行。

什么是迭代器?
class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    } else {
      return {done: true, value: undefined};
    }
  }
}

// 返回一个新的迭代器,可以从start到stop计数。
function range(start, stop) {
  return new RangeIterator(start, stop);
}

查看运行结果

生成器就是迭代器!所有的生成器都有内建.next()和[Symbol.iterator]()方法的实现。你只须编写循环部分的行为。

function* range(start, stop) {
  for (var i = start; i < stop; i++)
    yield i;
}

我们可以通过一个类比demo来进一步熟悉生成器,你可能对此感到陌生,但是并不难理解。

// 取货码1.0
function* ticketGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

var takeANumber = ticketGenerator()
console.log(takeANumber.next()) //{value: 1, done: false}
console.log(takeANumber.next()) //{value: 2, done: false}
console.log(takeANumber.next()) //{value: 3, done: false}
console.log(takeANumber.next()) //{value: undefined, done: true}
//取货码2.0
//无限循环取货码
function* ticketGenerator() {
  for(var i=0; true; i++) 
    yield i; //一定要打分号
}
var takeANumber = ticketGenerator()
console.log(takeANumber.next().value) //{value: 1, done: false}
console.log(takeANumber.next().value) //{value: 2, done: false}
console.log(takeANumber.next().value) //{value: 3, done: false}
console.log(takeANumber.next().value) //{value: 4, done: false}
//每一次当我们调用next()时,
//generator执行下一个循环迭代然后暂停。
//这意味着我们拥有一个可以无限向下运行的generator。
//因为这个generator只是发生了暂停,你并没有冻结你的程序。
//事实上,generator是一个创建无限循环的好方法。
//取货码2.2
//通过改变给next传递一个值,它会被视为generator中的一个yield语句的结果来对待。
function* ticketGenerator(){
    for(var i=0; true; i++){
        var reset = yield i;
        if(reset) {i = -1;}
    }
}
var takeANumber = ticketGenerator(); 
console.log(takeANumber.next().value); //0  
console.log(takeANumber.next().value); //1 
console.log(takeANumber.next().value); //2 
console.log(takeANumber.next(true).value); //0 
console.log(takeANumber.next().value); //1   

它与普通函数有很多共同点,但是二者有如下区别:

  • 普通函数使用function声明,而生成器函数使用function*声明。

  • 在生成器函数内部,有一种类似return的语法:关键字yield。二者的区别是,普通函数只可以return一次,而生成器函数可以yield多次(当然也可以只yield一次)。在生成器的执行过程中,遇到yield表达式立即暂停,后续可恢复执行状态。

这就是普通函数和生成器函数之间最大的区别,普通函数不能自暂停,生成器函数可以。

demo(斐波那契数列)

生成器函数和 yield 结合来生成斐波那契数列(前两个数字都是 1 ,除此之外任何数字都是前两个数之和的数列)

function fab(max) {
    var count = 0, last = 0, current = 1;

    while(count++ < max) {
        yield current;
        var tmp = current;
        current += last;
        last = tmp;
    }
}

for(var i of fib(10)) {
    console.log(i);
}

异步

Javascript语言的执行环境是"单线程"(single thread)。所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。

这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

  • 回调函数

  • 事件监听

  • 发布/订阅

  • Promises对象

f1().then(f2).fail(f3);

Promise

Promise有很多版本,也有很多实现的库,但是这里主要是介绍ES6标准的内容。如果阅读以下几条特性觉得不懂的话建议先看看上面两本书相应的章节。

关于promise,首先要意识到它是一种对象。这种对象可以用Promise构造函数来创建,也可以通过Nodejs本身一些默认的返回来获取这种对象。
promise对象有三种状态:Pending,Fulfilled,Rejected。分别对应着未开始的状态,成功的状态,以及失败的状态。
这种对象常常封装着异步的方法。在异步方法里面,通过resolve和reject来划定什么时候算是成功,什么时候算是错误,同时传参数给这两个函数。这些参数就是异步得到的结果或者错误。
异步有成功的时候,也有错误的时候。对象通过then和catch方法来规定异步结束之后的操作(正确处理函数/错误处理函数)。而then和catch是Promise.prototype上的函数,因此“实例化”之后(其实并非真正的实例)可以直接使用。
这个promise对象还有一个神奇的地方,就是可以级联。每一个then里面返回一个promise对象,就又像上面所提的那样,有异步就等待异步,然后选择出规定好的正确处理函数还是错误处理函数。

如何关停生成器

  • generator.return()

  • generator.next()的可选参数

  • generator.throw(error)

  • yield*

生成器可以用来实现异步编程,完成你用异步回调或promise链所做的一切。

生成器的.next()方法接受一个可选参数,参数稍后会作为yield表达式的返回值出现在生成器中。那就是说,yield语句与return语句不同,它是一个只有当生成器恢复时才会有值的表达式。

结合生成器实现更多功能

普通yield表达式只生成一个值,而yield*表达式可以通过迭代器进行迭代生成所有的值。

function* concat(iter1, iter2) {
 for (var value of iter1) {
    yield value;
  }
  for (var value of iter2) {
    yield value;
  }
}
function* concat(iter1, iter2) {
      yield* iter1;
      yield* iter2;
}
fetch(url, {
  method: "POST",
  body: JSON.stringify(data),
  headers: {
    "Content-Type": "application/json"
  },
  credentials: "same-origin"
}).then(function(response) {
  response.status     //=> number 100–599
  response.statusText //=> String
  response.headers    //=> Headers
  response.url        //=> String

  return response.text()
}, function(error) {
  error.message //=> String
})

回调地狱

/**
* 第一個是抓取文章列表的api
* 第二個是給文章id, 抓取文章內容的api
* 第三個是給作者id, 返回作者資訊的api
*/

getArticleList(function(articles){
    getArticle(articles[0].id, function(article){
        getAuthor(article.authorId, function(author){
            alert(author.email);
        })
    })
})

function getAuthor(id, callback){
    $.ajax("http://beta.json-generator.com/api/json/get/E105pDLh",{
        author: id
    }).done(function(result){
        callback(result);
    })
}

function getArticle(id, callback){
    $.ajax("http://beta.json-generator.com/api/json/get/EkI02vUn",{
        id: id
    }).done(function(result){
        callback(result);
    })
}

function getArticleList(callback){
    $.ajax(
    "http://beta.json-generator.com/api/json/get/Ey8JqwIh")
    .done(function(result){
        callback(result);
    });
}
getArticleList()
     .then(articles => getArticle(articles[0].id))
     .then(article => getAuthor(article.authorId))
     .then(author => {
       alert(author.email);
     });
    
    function getAuthor(id){
        return new Promise(function(resolve, reject){
            $.ajax("http://beta.json-generator.com/api/json/get/E105pDLh",{
                author: id
            }).done(function(result){
                resolve(result);
            })
        });
    }

    function getArticle(id){
        return new Promise(function(resolve, reject){
            $.ajax("http://beta.json-generator.com/api/json/get/EkI02vUn",{
                id: id
            }).done(function(result){
                resolve(result);
            })
        });
    }

    function getArticleList(){
        return new Promise(function(resolve, reject){
           $.ajax(
            "http://beta.json-generator.com/api/json/get/Ey8JqwIh")
            .done(function(result){
                resolve(result);
            }); 
        });
    }
// 利用Generator的特性,來写出很像同步但其实是非同步的代码
function* run(){
  var articles = yield getArticleList();
  var article = yield getArticle(articles[0].id);
  var author = yield getAuthor(article.authorId);
  console.log(author.email);  
}

var gen = run();
gen.next().value
    .then(articles => {
      gen.next(articles).value.then(article => {
        gen.next(article).value.then(author => {
          gen.next(author)
      })
    })
  })
async function run(){
  var articles = await getArticleList();
  var article = await getArticle(articles[0].id);
  var author = await getAuthor(article.authorId);
  alert(author.email);  
}

你可能感兴趣的:(拥抱Generator,告别异步回调)