Generator和Async

引言

接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术。

在异步编程中,还有一种常用的解决方案,它就是Generator生成器函数。顾名思义,它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。

Iterator接口

什么是Iterator接口

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

Iterator的作用

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

Iterator实现

function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
} var it = makeIterator(['a', 'b']); it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

原生具备Iterator接口的数据结构

  1. Array
  2. Map
  3. Set
  4. String
  5. TypedArray
  6. 函数的 arguments 对象
  7. NodeList 对象

查看一下Map下面的所挂载的Iterator

let map = new Map();
console.log(map.__proto__);

输出结果:

clear:ƒ clear()
constructor:ƒ Map()
delete:ƒ delete()
entries:ƒ entries()
forEach:ƒ forEach()
get:ƒ ()
has:ƒ has()
keys:ƒ keys()
set:ƒ ()
size:(...)
values:ƒ values()
Symbol(Symbol.iterator):ƒ entries()
Symbol(Symbol.toStringTag):"Map"
get size:ƒ size()
__proto__:Object

如何为Object部署一个Iterator接口

function iteratorObject(obj){
let keys = Object.keys(obj);
let index = -1;
return {
next(){
index++;
return index<keys.length?{
value:obj[keys[index]],
key:keys[index],
done:false
}:{
value:undefined,
key:undefined,
done:true
}
}
}
}
let obj = {a:1,b:2,c:3};
let iter = iteratorObject(obj);
console.log(iter.next());
// {value: 1, key: "a", done: false}
console.log(iter.next());
// {value: 2, key: "b", done: false}
console.log(iter.next());
// {value: 3, key: "c", done: false}
console.log(iter.next());
// {value: undefined, key: undefined, done: true}

通过上面的方法可以简单的为Object部署了一个Iterator接口。

Generator函数

Generator是ES6的新特性,通过yield关键字,可以让函数的执行流挂起,那么便为改变执行流程提供了可能。

Generator语法

dome:

function * greneratorDome(){
yield "Hello";
yield "World";
return "ending";
}
let grenDome = greneratorDome();
console.log(grenDome)

上面的代码中定义了一个Generator函数,获取到了函数返回的对象。下面是其输出结果。

原型链:

greneratorDome {<suspended>}
__proto__:Generator
__proto__:Generator
constructor:GeneratorFunction {prototype: Generator, constructor: ƒ, Symbol(Symbol.toStringTag): "GeneratorFunction"}
next:ƒ next()
return:ƒ return()
throw:ƒ throw()
Symbol(Symbol.toStringTag):"Generator"
__proto__:Object
[[GeneratorStatus]]:"suspended"
[[GeneratorFunction]]:ƒ* greneratorDome()
[[GeneratorReceiver]]:Window
[[GeneratorLocation]]:test.html:43
[[Scopes]]:Scopes[3]

通过上面的输出结果可以看的出来,沿着原型链向上查找就存在一个next方法,这个方法与Iterator接口返回的结果是大同小异的。

继续延续dome代码,并使用next方法向下执行。

function * greneratorDome(){
yield "Hello";
yield "World";
return "Ending";
}
let grenDome = greneratorDome();
console.log(grenDome.next());
// {value: "Hello", done: false}
console.log(grenDome.next());
// {value: "World", done: false}
console.log(grenDome.next());
// {value: "Ending", done: true}
console.log(grenDome.next());
// {value: undefined, done: true}

在最开始的地方有提到过Generator函数,最后返回的是一个Iterator对象,这也就不难理解了。

异步的Generator

dome

function a (){
setTimeout(() => {
alert("我是后弹出");
},1000)
}
function b (){
alsert("我是先弹出");
}
function * grenDome (){
yield a();
yield b();
}
let gren = grenDome();
gren.next();
gren.next();
// 输出结果
// 我是先弹出
// 我是后弹出

结合Promise

function a (){
return new Promise((resolve,reject) => {
setTimeOut(() => {
console.log(1)
resolve("a");
})
})
}
function b (){
return new Promise((resolve,reject) => {
console.log(2)
resolve("b");
})
}
function * grenDome (){
yield a();
yield b();
return new Promise((resolve,reject) => {
resolve("grenDome内部")
})
}
let gren = grenDome(); // console.log(gren.next())
// {value: Promise, done: false}
// console.log(gren.next())
// {value: Promise, done: false}
// console.log(gren.next())
// {value: Promise, done: true}
// console.log(gren.next())
// {value: undefined, done: true} gren.next().value.then((res) => {
console.log(res);
// a函数
})
gren.next().value.then((res) => {
console.log(res);
// b函数
})
gren.next().value.then((res) => {
console.log(res);
// grenDome内部
})
// 输出结果
// a
// b
// grenDome内部

在上面的代码中有一点是需要注意的,在grenDome函数里面最后return出去了一个Promise,但是在输出的时候虽然done属性已经为true但是value里面仍然会存有一个promise对象,实际上done表示的是对应yield关键字的函数已经遍历完成了。

Async/Await

Async/awaitJavascript编写异步程序的新方法。以往的异步方法无外乎回调函数和Promise。但是Async/await建立于Promise之上,换句话来说使用了Generator函数做了语法糖。

async函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案。

什么是Async/Await

async顾名思义是“异步”的意思,async用于声明一个函数是异步的。而await从字面意思上是“等待”的意思,就是用于等待异步完成。并且await只能在async函数中使用。

Async/Await语法

function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
};
asyncPrint('hello world',2000);
// 在2000ms之后输出`hello world`

返回Promse对象

通常asyncawait都是跟随Promise一起使用的。为什么这么说呢?因为async返回的都是一个Promise对象同时async适用于任何类型的函数上。这样await得到的就是一个Promise对象,如果不是Promise对象的话那async返回的是什么就是什么。

async function f() {
return 'hello world';
}
f().then(v => console.log(v));
// hello world

async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

function a(){
return new Promise((resolve,reject) => {
console.log("a函数")
resolve("a函数")
})
}
function b (){
return new Promise((resolve,reject) => {
console.log("b函数")
resolve("b函数")
})
}
async function dome (){
let A = await a();
let B = await b();
return Promise.resolve([A,B]);
}
dome().then((res) => {
console.log(res);
});

执行机制

前面已经说过await是等待的意思,之后等前面的代码执行完成之后才会继续向下执行。

function a(){
return new Promise((resolve,reject) => {
resolve("a");
console.log("a:不行")
})
}
function b (){
return new Promise((resolve,reject) => {
resolve("b");
console.log("b:不行");
})
}
async function dome (){
await a();
await b();
console.log("虽然我在后面,但是我想要先执行可以么?")
}
dome();
// 输出结果
// a:不行
// b:不行
// 虽然我在后面,但是我想要先执行可以么?

另外一个列子

function timeout1(ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("timeout1")
resolve();
},ms);
});
};
function timeout2(ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("timeout2");
resolve();
},ms);
});
};
async function asyncPrint() {
await timeout1(1000);
await timeout2(2000);
};
asyncPrint().then((res) => {
console.log(res);
}).catch((err) => {
console.log(err)
})
// 1s 后输出timeout1
// 3s 后输出timeout2
// undefined

async、await错误处理

JavaScript异步请求肯定会有请求失败的情况,上面也说到了async返回的是一个Promise对象。既然是返回一个Promise对象的话那处理当异步请求发生错误的时候我们就要处理reject的状态了。

在Promise中当请求reject的时候我们可以使用catch。为了保持代码的健壮性使用async、await的时候我们使用try catch来处理错误。

async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world');
} async function b() {
try {
await f();
} catch(err) {
console.log(err);
}
}
b();
// 出错了

总结

# Iterator接口

遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。

Es6提供很多API都是基于Iterator接口,比如解构,for...of循环,拓展运算等。

# Generator函数

调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。

value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束

# Async/Await

Async/await是近些年来JavaScript最具革命性的新特性之一。他让读者意识到使用Promise存在的一些问题,并提供了自身来代替Promise的方案。他使得异步代码变的不再明显,我们好不容易已经学会并习惯了使用回调函数或者then来处理异步。

从Iterator到async/await的更多相关文章

  1. ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await

    ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await co ...

  2. Python PEP 492 中文翻译——协程与async/await语法

    原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...

  3. [转] Understanding JavaScript’s async await

    PS:Promise的用处是异步调用,这个对象使用的时候,call then函数,传一个处理函数进去,处理异步调用后的结果 Promise<Action>这样的对象呢,异步调用后的结果是一 ...

  4. async/await,了解一下?

    上一篇博客我们在现实使用和面试角度讲解了Promise(原文可参考<面向面试题和实际使用谈promise>),但是Promise 的方式虽然解决了 callback hell,但是这种方式 ...

  5. promise async await使用

    1.Promise (名字含义:promise为承诺,表示其他手段无法改变) Promise 对象代表一个异步操作,其不受外界影响,有三种状态: Pending(进行中.未完成的) Resolved( ...

  6. 理解Python协程:从yield/send到yield from再到async/await

    Python中的协程大概经历了如下三个阶段:1. 最初的生成器变形yield/send2. 引入@asyncio.coroutine和yield from3. 在最近的Python3.5版本中引入as ...

  7. [翻译] Python 3.5中async/await的工作机制

    Python 3.5中async/await的工作机制 多处翻译出于自己理解,如有疑惑请参考原文 原文链接 身为Python核心开发组的成员,我对于这门语言的各种细节充满好奇.尽管我很清楚自己不可能对 ...

  8. JS异步编程 (2) - Promise、Generator、async/await

    JS异步编程 (2) - Promise.Generator.async/await 上篇文章我们讲了下JS异步编程的相关知识,比如什么是异步,为什么要使用异步编程以及在浏览器中JS如何实现异步的.最 ...

  9. async/await 实现协程

    2. 基本了解 在了解异步协程之前,我们首先得了解一些基础概念,如阻塞和非阻塞.同步和异步.多进程和协程. 2.1 阻塞 阻塞状态指程序未得到所需计算资源时被挂起的状态.程序在等待某个操作完成期间,自 ...

随机推荐

  1. python-使用阿里云镜像加速

    1. 在当前用户根目录下建立.pip文件夹 mkdir ~/.pip2.在.pip文件夹下创建文件pip.conf,并追加内容 [global]trusted-host=mirrors.aliyun. ...

  2. phpstudy5.6 No input file specified的解决方法

    一.问题描述 5.6就提示这个错误,切换5.5就可以 二.原因分析 原因1:提示:“No input file specified.”原因在于使用的PHP5.6是fast_cgi模式,而在某些情况下, ...

  3. mysql实现oracle存储过程默认参数

    我们都知道oracle存储过程支持为参数设置默认值,这样即使存储过程升级,原来的调用也可以不受影响.但是mysql不支持,mariadb也没有支持(截止10.4也是如此).但是这一限制会导致升级麻烦重 ...

  4. Android近场通信---NFC基础转)

    Android近场通信---NFC基础(一)(转) 本文介绍在Android系通过你所能执行的基本任务。它解释了如何用NDEF消息格式来发送和接收NFC数据,并且介绍了支持这些功能的Android框架 ...

  5. 014 ThreadLocal详解

    一:ThreadLocal的原理 1.说明 ThreadLocal从字面意思来理解,是一个线程本地变量,也可以叫线程本地变量存储.有时候一个对象的变量会被多个线程所访问,这个时候就会有线程安全问题,当 ...

  6. h5开发安卓机型点击输入框调起输入法,输入框被键盘遮挡的解决方法

    前言: 从以前的项目中找一个问题的解决方案,顺带找到了这个安卓机型调起输入法,页面没有自动上滑导致输入框被弹起的键盘遮挡的解决方案.这个问题只有安卓机型页面中的输入框处于底部(也就是底部键盘区域)的时 ...

  7. (4)Flask项目模板渲染初体验

    一.准备静态资源 将项目使用到的静态资源拷贝到static目录 二.创建前台首页html 创建templates/home/home.html页面,内容包含导航和底部版权两部分,中间内容区域为模板标签 ...

  8. WebGL第一步

    什么是WebGL? WebGL使用了GLSL ES(OpenGL ES)着色器语言,通过配合调用js相关的绘制接口来实现3D效果. 采用页面中的<canvas>元素来定义绘图区域,canv ...

  9. PHP字符串替换

    $pid = str_replace(',',',',$pid); $pid = str_replace(' ','',$pid); $pid = str_replace(array(',', ' ' ...

  10. 自己实现简单版SpringMVC

    SpringMVC的主要作用是:从http请求中得到一个url字符串和对应的请求参数,根据该字符串找到Controller中的一个方法,利用反射执行该方法,将结果返回给前端 1,初始化 将url请求路 ...