黑马程序员技术交流社区

标题: 迭代器与 async/await [打印本页]

作者: charcoal    时间: 2021-6-20 10:13
标题: 迭代器与 async/await
前言
本文主要介绍的是js中迭代器的实现原理,以及源码分析;async是如何以同步的方式编程的原理分析
前置知识
1. 线程、进程
进程是操作系统分配资源的最小单位, 线程事操作系统执行的最小单位。
我的理解就是进程是程序拥有CPU内相关资源的一段时间的描述,强调的是一个时间段内的环境。而线程则是在这个环境中做的某件事情的描述,强调的是做的过程。
2. 用户态与内核态先说说内核,就是直接管理计算机硬件的软件其实就是操作系统(内核)对cpu硬件资源管理上的权限分离。线程、进程的切换是用内核完成的,即内核态。而用户态就是我们程序运行的状态。
协程接下来就讲讲我们的主角:协程。先从比较开始,对比线程:



迭代器(iterator)es5中的迭代实现
我们来看看es5中是怎么实现的迭代功能的for of,看看babel转换的结果:
[JavaScript] 纯文本查看 复制代码
let arr = [1,2,3]
for(let it of arr){
  console.log(it)
}


babel转换结果如下,仅展示核心代码
[JavaScript] 纯文本查看 复制代码
var arr = [1, 2, 3];
var _iteratorNormalCompletion = true;
// 核心代码
for (var _iterator = arr[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
    var it = _step.value;

    console.log(it);
}


其中最关键的就是实现了数组内部的一个函数:[Symbol.iterator]
了解了for of的实现原理,我们来实现一个可迭代的对象:


[JavaScript] 纯文本查看 复制代码
function iteratorObj( obj={} ){
    return {
        ...obj,
        // 1. 构造`Symbol.iterator`函数
        [Symbol.iterator](){
            const keys = Object.keys(obj)
            let index = 0
            // 2. 返回一个包含next函数的对象
            return {
                next: function(){
                  // 3. 执行next,返回对象
                  return {
                      value: obj[keys[index]] || undefined,
                      done: !obj[keys[index++]]  // 4. 执行之后指针后移,继续迭代
                  }  
                },
            }
        }
    }
}

// Test case
var itObj = iteratorObj({ a:'1',b:'2' })
for(let it of itObj){
    console.log(it)
}
// 1
// 2


es6的迭代器
将迭代器之前,我们来看看生成器generator,看一段代码

[JavaScript] 纯文本查看 复制代码
function* fn(){
    yield 1;
    yield 2;
    yield 3;
}
var gen = fn();
gen.next();  // {value: 1, done: false}
gen.next();  // {value: 2, done: false}
gen.next();  // {value: 3, done: false}
gen.next();  // {value: undefined, done: true}
es6引入了生成器就是为了迭代而生的,下面我们用生成器来实现迭代功能,其实只要重写[Symbol.iterator]函数,我们拿上一步的代码改造

[JavaScript] 纯文本查看 复制代码
function iteratorObj( obj={} ){
    return {
        ...obj,
        // 1. 构造`Symbol.iterator`生成器函数
        *[Symbol.iterator](){
            const keys = Object.keys(obj)
            // 由链式变成了线性 遍历
            for (let i of keys){
                yield obj
            }
        }
    }
}

异步编程async/await 初探
es6新增的promise是现代异步编程的解决方案。但promise写法仍然避免不了回调嵌套。比如下面:

[JavaScript] 纯文本查看 复制代码
function requestSystemData(){
    fetch(url).then((res)=>{
        //.....
       fetch(url2).then(res=>{
           //...
           fetch(url3).then(res=>{
               // ...
           })
       })
    })
}


于是es7推出了async/await,改写以上代码

[JavaScript] 纯文本查看 复制代码
async function requestSystemData(){
    const [res] = await fetch(url)
    //...
    const [res2] = await fetch(url2)
    //...
    const [res3] = await fetch(url3)
    //...
}

生成器(generator)
async 函数是什么?一句话,它就是 Generator 函数的语法糖。最关键的生成器generator函数,下面我们就重点考察
先看看一个简单的生成器函数
[JavaScript] 纯文本查看 复制代码
function * fn(){
  console.log(1)
  yield 1
  console.log(2)
  yield 2
  console.log(3)
  yield 3
}
var gen = fn();
gen.next()   //{ value: 1, done: false }
gen.next()   //{ value: 2, doen: false }
gen.next()   //{ value: 3, doen: false }
gen.next()   //{ value: undefined, doen: true }

继续我们看看es5中是如何实现的:

[JavaScript] 纯文本查看 复制代码
"use strict";

var _marked = [fn].map(regeneratorRuntime.mark);

function fn() {
  return regeneratorRuntime.wrap(function fn$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          console.log(1)
          _context.next = 2;
          return 1;

        case 2:
          console.log(2)
          _context.next = 4;
          return 2;

        case 4:
          console.log(3)
          _context.next = 6;
          return 3;

        case 6:
        case "end":
          return _context.stop();
      }
    }
  }, _marked[0], this);
}

var gen = fn();
gen.next();
gen.next();
gen.next();
继续我们看看es5中是如何实现的:

[JavaScript] 纯文本查看 复制代码
"use strict";

var _marked = [fn].map(regeneratorRuntime.mark);

function fn() {
  return regeneratorRuntime.wrap(function fn$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          console.log(1)
          _context.next = 2;
          return 1;

        case 2:
          console.log(2)
          _context.next = 4;
          return 2;

        case 4:
          console.log(3)
          _context.next = 6;
          return 3;

        case 6:
        case "end":
          return _context.stop();
      }
    }
  }, _marked[0], this);
}

var gen = fn();
gen.next();
gen.next();
gen.next();

我们简单分析一下,核心的就两点:
那么接下来就简单的模拟实现一下,忽略旁枝末节:

[JavaScript] 纯文本查看 复制代码
// 1. 拿到babel转换分割后的代码
function fn$ (_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 1;

        case 2:
          _context.next = 4;
          return 2;

        case 4:
          _context.next = 6;
          return 3;

        case 6:
        case "end":
          return _context.stop();
      }
    }
}
// 2.实现上下文的包装函数
function regeneratorRuntime(){
    const self = this
    // 上下文对象
    this.context = {
        prev:0,
        next:0,
        done: false,
        stop:function(){
            self.context.done = true
        },
    }
    this.wrap = function(fn){
        return {
            next: function(){
                return {
                    value: fn(self.context),
                    done: self.context.done //如果是context.stop(),则返回
                }
            }
        }
    }
}

// Test case
var runtime = new regeneratorRuntime()
var gen = runtime.wrap(fn$)
console.log(gen.next()){ value: 1, done: false }
console.log(gen.next()) //{ value: 2, done: false }
console.log(gen.next()) //{ value: 3, done: false }
console.log(gen.next()) //{ value: undefined, done: true }


小结:


到了es6,v8底层支持了生成器的写法,所以也不再需要js层面的语法糖(babel转译)。
async/await实现
es2017推出了async,其实就是generator + promise的语法糖。

[JavaScript] 纯文本查看 复制代码
async function fn(){
    console.log(1)
    await new Promise(res=> setTimeout(function(){res(1)},500))
    console.log(2)
    await new Promise(res=> setTimeout(function(){res(2)},500))
    console.log(3)
}
console.log(0)
fn()
console.log(4)

// 0
// 1
// 4
// 2
// 3
下面我们用生成器、promise语法来实现async语法糖代码:
[JavaScript] 纯文本查看 复制代码
// 1. 将async转译成*函数
function fn*(){
    console.log(1)
    yiel new Promise(res=> setTimeout(function(){res(1)},500))
    console.log(2)
    yield new Promise(res=> setTimeout(function(){res(2)},500))
    console.log(3)
}

//2. 语法糖转化代码
function generatorToAsync(fn){
    return function(){
        //生成生成器
        var gen = fn();
        //执行async返回的是一个Promise对象
        return new Promise((resolve,reject)=>{
            var step = function(){
                var info = gen.next()
                var { done, value } = info
                if(done) return resolve(value)
                //将yield后面的值,包装为Promise对象
                return Promise.resolve(value).then(value=>{
                    step()
                })
            }
            //启动生成器
            step()
        })
        
    }
}
至此,async函数实现了异步方法的同步编程,并且能够顺序执行函数内的所有异步方法。
总结










欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2