ES6-Async & 异步
依赖文件地址 :https://github.com/chanceLe/ES6-Basic-Syntax/tree/master/js
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>[es6]-16-异步操作和Async函数</title>
<script src="./js/browser.js"></script>
<script src="./js/babel-pollyfill.js"></script>
<script type="text/babel">
/*
* 异步编程对js语言太重要。js语言的执行环境是单线程的,如果没有异步编程,根本没法用。
*
* ES6以前,异步编程的方法,大概有四种:
* 回调函数 事件监听 发布/订阅 Promise对象
*
* ES6将js异步编程带入了一个新阶段,ES7的Async函数更是提出了异步编程的终极解决方案。
*
* 所谓异步,就是一个任务分成两段,先执行一段,然后转而执行其他任务,等做好了准备,再回头执行第二段。
* 这种不连续的执行,就叫异步。
* 相应的,连续的执行叫同步,由于是连续执行,所以要等待IO,这时候程序暂停,cpu浪费。
*
* 回调函数:
* js语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面。
* 等到重新执行这个任务的时候,就直接调用这个函数。
* 回调函数不多说,这里只说Node.js约定,回调函数的第一个参数是err,原因是执行分成两段,这两段之间抛出的
* 错误,程序无法捕捉,只能当做参数,传入第二段。
*
* Promise:
* 回调函数本身并没有问题,问题出在多个回调函数嵌套。如果多重嵌套,代码就横向发展,很快就乱成一团,这种
* 情况称为 回调函数噩梦。
* Promise就是为了解决回调函数噩梦的问题提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,
* 改成链式调用。
*
* Promise 的写法只是回调函数的改进,使用then方法后,异步任务的两段执行看的更清楚了,除此之外,并无新意。
* Promise的最大问题是代码冗余,原来的任务被Promise包装了以下,不管什么操作,一眼看去都是then,原来的语义
* 变得很不清楚。
*
* 更好的写法...
* Generator函数
* 协程:传统的编程语言,早有异步编程解决方案。其中有一种叫做协程,意思就是多个线程互相协作,完成异步任务。
* 协程有点像函数,又有点像线程。运行流程大致如下:
* 第一步,协程A开始执行。
* 第二步,协程A执行到一半,进入暂停,执行权转到协程B。
* 第三步,(一段时间后)协程B交还执行权。
* 第四步,协程A恢复执行。
* 举例来说,读取文件的协程写法如下:
* function* asyncJob(){
* // 其他代码
* var f = yield readFile(fileA);
* //其他代码
* }
* 上面代码的奥妙在于其中的yield命令。它表示执行到此处,执行权交给其他协程。也就是说yield是异步两个阶段的分界线。
* 协程遇到yield命令会暂停,等到执行权返回,再从暂停的地方继续往后执行。它最大的优点是代码的写法
* 非常像同步操作,除了yield命令,几乎一模一样。
*
* Generator函数的概念
* Generator函数是协程在ES6的实现,最大的特点就是可以交出函数的执行权(即暂停执行)。
* 整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。
*
*
* 异步任务的封装:
* var fetch = require("node-fetch");
* function *gen(){
* var url = 'https://api.github.com/users/github';
* var result = yield fetch(url);
* console.log(result.bio)
* }
*
* 上面代码中,Generator函数封装了一个异步操作,该操作先读取一个远程接口,然后从json格式的数据解析信息。
* 执行这段代码的方法如下:
* var g = gen();
* var result = g.next();
* result.value.then(function(data){
* return data.json();
* }).then(function(data){
* g.next(data);
* })
*
* 虽然Generator函数将异步操作表示的很简洁,但是流程管理却不方便,(什么时候执行第一段,什么时候执行第二段)
*/ /*
* Thunk函数
* 参数的求值策略:
* Thunk函数在上世纪60年代就诞生了。那时,编程语言刚起步,计算机科学家还在研究,编译器怎么写比较好。
* 争论的一个焦点是 求值策略,即函数的参数到底应该何时求值。
*
* 一种意见是传值调用,即在进入函数体之前,就计算参数表达式的值。
* 另一种是传名调用,直接将函数的参数表达式传入函数体,只在用到它的时候求值,Haskell语言采用这种策略。
*
* 传值调用比较简单,时但是催参数求值的时候,实际上还没用到这个参数,有可能造成性能损失。
*
* Thunk函数的含义:
* 编译器的传名调用的实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫做Thunk函数。
*/
{
var x = 2;
function f(m){
return m*2;
}
console.log(f(x+5)); //14
//等同于
var thunk = function(){
return x+5;
}
function f1(thunk){
return thunk()*2;
} //上面代码中,函数f的参数x+5被一个函数替换了。凡是用到原参数的地方,对thunk函数求值即可。
}
//这就是thunk函数的定义:它是传名调用的一种实现策略,用来替换某个表达式。 /*
* js中的Thunk函数
* js是传值调用,它的Thunk函数含义有所不同。在js语言中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本
* ,且只接受回调函数作为参数。
*/
/*
{
//正常版本的readFile(多参数版本)
fs.readFile(filename,callback);
//Thunk版本的readFile(单参数版本)
var readFileThunk = Thunk(fileName);
readFileThunk(callback); var Thunk = function(fileName){
return function(callback){
return fs.readFile(fileName,callback);
}
} //上面代码中,fs模块的readFile方法是一个多参数函数,两个参数分别是文件名和回调函数。
//经过转换器处理,它变成了一个单参数函数,只接受回调函数作为参数。
//这个单参数版本,就叫做Thunk函数。
}
//任何函数,只要参数有回调函数,就能协程Thunk函数的形式。下面是一个简单的Thunk函数转换器 {
//es5版本
var Thunk = function(fn){
return function(){
var args = Array.prototype.slice.call(arguments);
return function(callback){
args.push(callback);
return fn.apply(this,args);
} }
}
}
{
//es6 版本
var Thunk = function(fn){
return function(...args){
return function(callback){
return fn.call(this,...args,callback);
}
}
}
}
*/
/*
* Thunk函数在以前确实没什么用,但有了ES6的Generator函数之后,Thunk函数可以用于Generator函数的
* 自动流程管理。
*/
//Generator函数可以自动执行 {
function* gen(){
//some code
yield 1;
yield 2;
yield 3;
yield 4;
}
var g = gen();
var res = g.next(); while(!res.done){
console.log(res.value);
res = g.next();
}
}
/*
上面代码中,Generator函数gen会自动执行完所有步骤。但是,这并不适合异步操作。
如果必须保证前一步执行完,后一步才执行,那上面的自动执行就不可行。这时Thunk函数就能派上用场。
在Generator函数中,yield命令用于将程序的执行权移出Generator函数,那么需要一种方法将
执行权还给Generator函数。这种方法就是Thunk函数,因为它可以在回调函数里,将执行权交还给
Generator函数。
*/ //Thunk函数真正的威力,在于可以自动执行Generator函数。
//下面是一个基于Thunk函数的Generator执行器。 function run(fn){
var ge = fn();
function next(err,data){
var result = ge.next(data);
if(result.done) return;
// result.value(next);
console.log(result.value);
result.value = next(); }
next();
}
function* g4(){
yield 1;
yield 2;
yield 3;
yield 4;
}
run(g4);
/*
* 上面的代码的run函数,就是一个Generator函数的自动执行器。内部的next函数就是Thunk的
* 回调函数。next先将指针移到Generator函数的下一步。然后判断Generator函数是否结束,如果没结束,就将
* next函数,再传入Thunk函数(result.value属性),否则直接退出。
* 有了这个执行器,执行Generator函数方便多了。不管内部有多少个异步操作,直接把Generator函数传入
* run函数即可。当然前提是,每个异步操作都要是Thunk函数,也就是说跟在yield命令后面的必须是Thunk函数。
*
* var g = function*(){
* var f1 = yield readFile("fileA")
* var f2 = yield readFile("fileB")
* var f3 = yield readFile("fileC")
* ...
* var f4 = yield readFile("fileD")
* }
* run(g);
*
* 上面代码中,函数g封装了n个异步的读取文件操作,只要执行run函数,这些操作就会自动完成。
* 这样一来,异步操作不仅可以写的像同步操作,而且一行代码就可以执行。
*
* Thunk函数并不是唯一自动执行Generator函数的方案。因为自动执行的关键是,必须有一种机制,
* 自动控制Generator函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise对象也可以做到这一点。
*/ /*
* co模块
* 是著名程序员TJ Holowaychuk于2013年6月发布的小工具,用于Generator函数的自动执行。
* 比如有一个Generator函数,co模块可以让你不用编写Generator函数的执行器。
* var co = require('co');
co(gen);
上面代码中,Generator函数只要传入co函数,就会自动执行。
co函数返回一个Promise对象,因此可以用then方法添加回调函数。 co(gen).then(function(){
console.log("Generator函数执行完成。")
}) co模块的原理?
Generator就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。
两种方法可以做到这点:
1.回调函数。 将异步操作包装成Thunk函数,在回调函数里面交回执行权。
2.Promise对象。将异步对象包装成Promise对象,用then方法交回执行权。 co模块其实就是将两种自动执行器(Thunk函数和Promise对象),包装成一个模块。
使用co的前提条件是,Generator函数的yield后面,只能在是Thunk函数或Promise对象。
*/ /*
*基于Promise对象的自动执行。
* 跟Thunk函数的差别就是用then方法,层层添加回调函数具体实现如下
*/
/*
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if(result.done){
return result.value;
}
result.value.then(function(data){
next(data);
})
}
next();
} run(gen);
//上面代码中,只要Generator函数还没执行到最后一步,next函数就调用自身,以此实现自动执行。
* */ /*
* co模块是Generator自动执行器的扩展,源码只有几十行非常简单。
* 首先,co函数接受Generator函数作为参数,返回一个Promise对象。 function co(gen){
var ctx = this;
return new Promise(function(resolve,reject){
})
}
*/
/*
* 在返回的Promise对象里面,co先检查函数gen是否为Generator函数。如果是,就执行该函数,得到一个内部指针对象
* 如果不是就返回,并将Promise的状态改为resolved。 function co(gen){
var ctx = this;
return new Promise(function(resolve,reject){
if(typeof gen === 'function'){
gen = gen.call(ctx);
}
if(!gen || typeof gen.next !== 'function'){
return resolve(gen);
}
})
}
*/ /*
* 接着,co将Generator函数的内部指针对象的next方法,包装成onFullfilled函数。
* 这主要是为了能够捕捉抛出的错误。
*/ function co(gen){
var ctx = this;
return new Promise(function(resolve,reject){
if(typeof gen === 'function'){
gen = gen.call(ctx);
}
if(!gen || typeof gen.next !== 'function'){
return resolve(gen);
} onFullfilled();
function onFullfilled(res){
var ret;
try {
ret = gen.next(res);
}catch(e){
return reject(e);
}
next(ret);
}
})
}
//最后,就是关键的next函数,它会反复调用自身。
function next(ret){
if(ret.done){
return resolve(ret.value);
}
var value = toPromise.call(ctx,ret.value);
if(value && isPromise(value)){
return value.then(onFullfilled,onRejected);
}
return onRejected(new TypeError("You may only yield a function,promise,generator,"
+"array or object,but the following object was passed:'"+String(ret.value)+"'"));
} /*
* 上面代码中,next函数的内部代码,
* 第一行,检查当前行为是否为Generator函数的最后一步,如果是就返回。
* 第二行确保每一步的返回值,是Promise对象。
* 第三行,使用then方法,为返回值加上回调函数,然后通过onFullfilled函数再次调用next函数。
*/ /*
* 处理并发的异步操作
* co支持并发的异步操作,即允许某些操作同时进行,等到他们全部完成,才进行下一步。
* 这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。
*
*/
//数组的写法
co(function*(){
var res = yield [
Promise.resolve(1),
Promise.resolve(2)
];
console.log(res);
}).catch(onerror);
//对象的写法
co(function*(){
var res = yield {
1:Promise.resolve(1),
2:Promise.resolve(2)
};
console.log(res);
}).catch(onerror); //下面还有一个例子
co(function*(){
var values = [n1,n2,n3];
yield values.map(somethingAsync);
});
function* somethingAsync(x){
//do something async
return y;
}
//上面的代码允许并发三个somethingAsync异步操作,等到他们全部完成,才会进行下一步。
</script>
</head>
<body>
</body>
</html>
ES6-Async & 异步的更多相关文章
- ES6/7 异步编程学习笔记
前言 在ES6的异步函数出现之前,Js实现异步编程只有settimeout.事件监听.回调函数等几种方法 settTmeout 这种方法常用于定时器与动画的功能,因为其本质上其实是浏览器的WebAPI ...
- ES6 async await
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 异步编程系列第01章 Async异步编程简介
p { display: block; margin: 3px 0 0 0; } --> 2016.10.11补充 三个月过去了,回头来看,我不得不承认这是一系列失败的翻译.过段时间,我将重新翻 ...
- @Async异步注解与SpringBoot结合使用
当你在service层需要启动异步线程去执行某些分支任务,又不希望显式使用Thread等线程相关类,只想专注于实现业务逻辑代码开发,可以使用@Async异步注解. 1. 使用@Async 异步注解 C ...
- C#.NET使用Task,await,async,异步执行控件耗时事件(event),不阻塞UI线程和不跨线程执行UI更新,以及其他方式比较
使用Task,await,async,异步执行事件(event),不阻塞UI线程和不跨线程执行UI更新 使用Task,await,async 的异步模式 去执行事件(event) 解决不阻塞UI线程和 ...
- spring boot(17)-@Async异步
验证码的异步机制 上一篇讲过可以用邮件发验证码,通常我们在某网站发验证码时,首先会提示验证码已发送,请检查邮箱或者短信,这就是图中的1和3.然而此时查看邮箱或短信可能并没有收到验证码,往往要过几秒种才 ...
- 在 .NET 4.0 下编写扩展代码以支持 async 异步编程
微软在C# 5中引入了async.await这两个异步编程的关键字,要使用这两个关键字需要你的IDE支持C#5.0语法,也就意味着你需要使用VS 2012版本以上IDE,或者在Vs2010卸载其编译器 ...
- Spring Boot (18) @Async异步
通常我们在某网站发送邮件验证码时,首先会提示验证码已发送,然而此时可能没有收到验证码,过几秒种才真正的收到.如果是同步会先验证发送是否成功然后再通知,如果是异步可以先通知用户已发送,并释放请求,然后再 ...
- @Async 异步注释 @EnableAsync
@SpringBootApplication @ComponentScan(basePackages = "com.fddsfsg") //@EnableSwagger2 @Ena ...
- 第六节:前后端交互之axios用法及async异步编程
一. axios用法 参考: API文档: https://www.kancloud.cn/yunye/axios/234845 GitHub: https://github.com/axios/ax ...
随机推荐
- Vivado安装教程
Vivado的各个版本的安流程其实都差不多,本教程用Vivado2016.4为例进行安装,同样适用于之前和之后的各个版本. 下载好安装包后打开,双击xsetup.exe运行安装程序 弹出的窗口,提示现 ...
- Android-Camera+SurfaceView
Camera相机是属于硬件,每台设备的Camera硬件配置的参数都是不一样的,Camera通常是用来拍照,扫描二维码等等 AndroidManifest.xml配置Camera需要的权限: <! ...
- linux apache安装https证书
1.首先查看是否安装apache 命令:rpm -qa|grep httpd 如果存在,卸载命令:rpm -e XXXX 如果不存在,安装命令:yum install httpd 安装完apache之 ...
- 微服务编译、启动jar命令指定配置文件
nohup java -Xms512m -Xmx8g -Xmn512m -Xss512k -server -XX:+HeapDumpOnOutOfMemoryError -jar smp-bill-c ...
- maven项目搜索依赖jar包顺序
local_repo > settings_profile_repo > pom_profile_repo > pom_repositories > setti ...
- C#——Socket
最近公司让我搞个socket小程序(服务端). 主要的功能:客户端发字符串到服务端,服务端接收后在界面上显示. 参照了网上许多代码,自己从中修改整理代码. public Form4() { Initi ...
- WPF上下滚动字幕
XAML代码: <local:WorkSpaceContent x:Class="SunCreate.CombatPlatform.Client.NoticeMarquee" ...
- js ajax请求传token
js ajax请求传token 方法一: headers: { Authorization: "BasicAuth " + token } 方法二: beforeSend: fu ...
- @media媒体查询
@media媒体查询 @media screen and (min-width:640px) and (max-width:1920px){/*当屏幕尺寸大于640px时与小于1920时*/ .pub ...
- BZOJ4283: 魔法少女伊莉雅(最短路径图+最短路径树)
题面 传送门 题解 太长了不想写了→_→ 题解 //minamoto #include<bits/stdc++.h> #define R register #define inf 0x3f ...