为了账号安全,请及时绑定邮箱和手机立即绑定

逐步加深的异步操作(中)

距离上一篇文章问世已经悄然过去了几个月,这几个月来。我自己有些懈怠,外加上失眠的一些症状搞的我精神差不多快崩溃了。但是现在差不多缓过来了。最近已经逐步的开始恢复更新的节奏了,虽然在写这篇文章的我哈欠打的不停,但是为了我心中的一些骄傲,来年前许下的一些承诺,我还是义无反顾的坚持了下来。

如果有读者对这篇文章之前的那些技术点不太清楚的话请参考逐步加深的异步操作(上)

##一、 Iterator

遍历器的概念:

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

遍历器的概念:

  • 一是为各种数据结构,提供一个统一的、简便的访问接口;
  • 二是使得数据结构的成员能够按某种次序排列;
  • 三是ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费。

基本数据结构的转变:

数组、某些类似数组的对象、Set和Map结构具备原生的遍历器结构。转化成这个可以直接使用

{
	let arr = [1,2];
	let it = arr[Symbol.iterator]();
	console.log(it.next())
	console.log(it.next())
	console.log(it.next())

}

效果图如下:
效果图

对象的转变

对象(Object)之所以没有默认部署Iterator接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。

本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。

不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作Map结构使用,ES5没有Map结构,而ES6原生提供了。

(1).一个对象如果要有可被for…of循环调用的Iterator接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

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};
      }
    }
  }

  function range(start, stop) {
    return new RangeIterator(start, stop);
  }

  for (var value of range(0, 3)) {
    console.log(value); // 0 1 2
  }

(2).代码首先在构造函数的原型链上部署Symbol.iterator方法,调用该方法会返回遍历器对象iterator,调用该对象的next方法,在返回一个值的同时,自动将内部指针移到下一个实例。

 function Obj(value) {
      this.value = value;
      this.next = null;
    }

    Obj.prototype[Symbol.iterator] = function() {
      var iterator = {
        next: mynext
      };

      var current = this;

      function mynext() {
        if (current) {
          var value = current.value;
          current = current.next;
          return {
            done: false,
            value: value
          };
        } else {
          return {
            done: true
          };
        }
      }
      return iterator;
    }

    var one = new Obj(1);
    var two = new Obj(2);
    var three = new Obj(3);

    one.next = two;
    two.next = three;

    for (var i of one){

      console.log(i); // 1 2 3
    }

(3).类似数组的对象调用数组的Symbol.iterator方法的例子:

  let iterable = {
      0: 'a',
      1: 'b',
      2: 'c',
      length: 3,
      [Symbol.iterator]: Array.prototype[Symbol.iterator]
    };
    for (let item of iterable) {
      console.log(item); // 'a', 'b', 'c'
    }

(4).普通对象部署数组的Symbol.iterator方法,并无效果:

let iterable = {
      a: 'a',
      b: 'b',
      c: 'c',
      length: 3,
      [Symbol.iterator]: Array.prototype[Symbol.iterator]
    };
    for (let item of iterable) {
      console.log(item); // undefined, undefined, undefined
    }

for…of…

一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以用for…of循环遍历它的成员。也就是说,for…of循环内部调用的是数据结构的Symbol.iterator方法。

for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的   Generator对象,以及字符串。
 1.for…of循环可以代替数组实例的forEach方法:

  const arr = ['red', 'green', 'blue'];

  arr.forEach(function (element, index) {
    console.log(element); // red green blue
    console.log(index); // 0 1 2
  });

  for(let i of arr){

    console.log(i); //  red green blue

  }

2.JavaScript原有的for…in循环,只能获得对象的键名,不能直接获取键值。ES6提供for…of循环,允许遍历获得键值:

var arr = ['a','b','c','d'];

  for(let a in arr){

    console.log(a); // 0 1 2 3

  }

  for(let a of arr){

    console.log(a); // a b c d

  }

3.for…of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for…in循环也不一样:

  let arr = [3,5,7];

  arr.hello = 'hello';

  for(let i in arr){

    console.log(i); // "0","1","2","3"

  }

  for(let i of arr){

     console.log(i); // "3","5","7"

  }

##二、 Generator

相信看完迭代器的基础之后,现在来开始讲Generator会有一定的优势,因为Generator是根据迭代器来操作的,来看下面一段代码:

function* Test() {
    yield 100
    yield (function () {return 300})()
    return 300
}

var test = Test()
console.log(typeof test)  // object

console.log(test.next())  // { value: 100, done: false }
console.log(test.next())  // { value: 300, done: false }
console.log(test.next())  // { value: 300, done: true }
console.log(test.next())  // { value: undefined, done: true }

由上面的代码可以晓得:

  • 定义Generator时,需要使用function *,其他的和普通函数一样。内部使用yield.
  • 执行var test = Test()生成一个Generator对象,并且typeof test发现不是普通的函数
  • 执行Test()之后,Test内部的代码不会立即执行,而是出于一个暂停状态.
  • 执行第一个test.next()时,会激活刚才的暂停状态,开始执行Test内部的语句,一直晕运行到下一个yield。一旦遇到yield语句时,它就会将yield后面的表达式执行,并返回执行的结果,然后又立即进入暂停状态。
  • 执行第一个next,打印出来的是{ value: 100, done: false },value是第一个yield返回的值,done: false表示目前处于暂停状态,尚未执行结束,还可以再继续往下执行。
  • 执行第二个next和第一个一样,不在赘述。此时会执行完第二个yield后面的表达式并返回结果,然后再次进入暂停状态
  • 执行第三个next时,程序会打破暂停状态,继续往下执行,但是遇到的不是yield而是return。这就预示着,即将执行结束了。因此最后返回的是{ value: 300, done: true },done: true表示执行结束,无法再继续往下执行了。
  • 再去执行第四次next时,就只能得到{ value: undefined, done: true },因为已经结束,没有返回值了。

那讲了这么多,如何用Generator做异步操作呢?先看下面一段代码:

function* Test2() {
    const r1 = yield dosomething1()
    console.log(r1)  
    const r2 = yield dosomething2()
    console.log(r2)  
    const r3 = yield dosomething3()
    console.log(r3) 
    const r4 = yield dosomething4()
    console.log(r4) 
}

这样写就能构成最基本的异步操作了。当不执行next的时候,该函数将处于暂停状态。当然这一节是一个介绍的,我也就来简单介绍一下,它的应用吧:

  • next和yield参数传递

yield具有返回数据。

function* Test3() {
    yield 100
}
const t = Test3()
console.log( t.next() ) // {value: 100, done: false}

next向yield传递

function* Test4() {
    const a = yield 100
    console.log('a', a)  // a aaa
    const b = yield 200
    console.log('b', b)  // b bbb
    const c = yield 300
    console.log('c', c)  // c ccc
}
const t4 = Test4()
t4.next()    // value: 100, done: false
t4.next('aaa') // value: 200, done: false
t4.next('bbb') // value: 300, done: false
t4.next('ccc') // value: undefined, done: true

上面值得注意的就是:t4.next('aaa')是将'aaa'传递给上一个已经执行完了的yield语句前面的变量,而不是即将执行的yield前面的变量.

  • 注意点

很多人看我这样写代码:

function* Test5() {}
const t5 = Test5()

会以为Generator是一个函数,然则不是,Generator并不是函数,更不是构造函数,不信来看下面一段代码:

function* G() {
    this.a = 10
}
const g = G()
console.log(g.a) // undefined

只有构造函数才会用这种方法,构造函数返回的是this,而Generator返回的是一个Iterator对象。这一点希望读者能够认识清楚。

三、 aysnc和await

首先这个东西呢?我是在封装自己的组件的什么看到的,属于es7的语法能够极大程度上解决promise代码臃肿的问题。今天我在这里就当做一个初学者,来探索一下aysncawait
首先至于这个的用法呢?就是在函数前面定义一个aysnc参数。来看下面一段代码:

async function timeout(){
	return "hello world!";
}

console.log(timeout());
console.log("outside console.log!!")

效果图如下:
效果图
从上面的代码可以发现。aysnc定义下的函数返回的是一个Promise对象,为了确认我这个想法,我决定来做一个大胆的猜测。

async function timeout(){
	return "hello world!";
}

timeout().then(result=>{
	console.log(result)
})

console.log(timeout())

console.log("outside console.log!!")

效果图如下:
效果图
由上面的代码我们可以确认两件事儿:第一:是aysnc定义下的函数是不会阻塞代码的正常运行的;第二:会返回一个promise对象。当然我们也可以继续来探讨一下,当aysnc定义下的函数抛出错误会怎么样呢?

async function timeout(val){
	if(val){
		return "success!!!"
	}
	throw "fail"
}


console.log(timeout(true))
console.log(timeout(false))

console.log("outside console.log!!")

效果图如下:
效果图
我们在chrome的console里面可以清楚的看到效果,当然我们也可以用catch来把这个进行拦截操作。
前面大概也讲完了aysnc,现在来讲一讲await吧。来看一段代码


async function timeout(){
	let result = await delayTime(3);
	console.log(result);
}

function delayTime(time){
	return new Promise((res,rej)=>{
		setTimeout(()=>{
			res("hello world");
		},time*1000)
	})
}

timeout()
console.log("outside console.log!!")

效果图如下:
效果图

其实我对于aysncawait的理解就是,aysnc关键字会将函数块内部定义成一个同步快,而await相当于给其上锁/阻塞,直到有promise返回才会执行下一步的操作。

说在最后:

异步操作是一个小白走向大牛的必经之路,一个明白异步操作的小伙子说明你的能力已经脱离了rookie的水平,一个熟练掌握异步的程序员其实已经差不多是一个熟手的地步。异步很难,但是使用起来确实能解决很多问题,掌握异步的方法却很简单,无非就是:大胆使用,小心求证罢了。
最后一章呢?我准备来重点讲述修饰器,因为这个东西,我直到现在都没敢使用,我感觉很有趣,准备花个大篇幅和大家进行讨论。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消