ECMAScript6(16):异步编程
2017-06-18 15:37
363 查看
ECMAScript6 (1):块级作用域
ECMAScript6 (2):解构赋值
ECMAScript6 (3):数值类型扩展
ECMAScript6 (4):字符串类型扩展
ECMAScript6 (5):函数的扩展
ECMAScript6 (6):数组的扩展
ECMAScript6 (7):二进制数组
ECMAScript6 (8):对象的扩展
ECMAScript6 (9):正则表达式的扩展
ECMAScript6 (10):Symbol基本类型
ECMAScript6 (11):Set 与 Map
ECMAScript6 (12):Proxy 和 Reflect
ECMAScript6 (13):Generator 函数
ECMAScript6 (14):iterator 迭代器
ECMAScript6 (15):Promise 对象
比如我们需要请求一个网络资源,由于网速比较慢,同步编程就意味着用户必须等待下载处理结束才能继续操作,所以用户体验极为不好;如果采用异步,下载进行中用户继续操作,当下载结束了,告诉用户下载的数据,这样体检就提升了很多。因此异步编程十分重要。
从计算机的角度来讲,js 只有一个线程,如果没有异步编程那一定会卡死的!异步编程主要包括以下几种:
回调函数
事件监听
发布/订阅模型
Promise对象
ES6异步编程
比如这里的这个文件读取,定义了一个回调函数,在读取文件成功或失败是调用,并不会立刻调用。
如同之前在 Promise 中提到的,当我想不断的读入多个文件,就会遇到回调函数嵌套,书写代码及其的不方便,我们称之为”回调地狱”。因此 ES6 中引入是了 Promise 解决这个问题。具体表现参看之前的 Promise 部分。但是 Promise 也带来了新的问题,就是代码冗余很严重,一大堆的 then 使得回调的语义不明确。
通过一个 Generator 函数的 yield, 可以将一个协程中断,去执行另一个协程。我们可以换一个角度理解 Generator 函数:它是协程在 ES6 中的具体体现。我们可以简单写一个异步任务的封装:
这个函数返回25肯定没错,但是,我们传给函数 fun 的参数在编译时到底保留
**但是 js 不是这样的!**js 会把多参数函数给 Thunk 了,以减少参数:
这里任何具有回调函数的函数都可以写成这样的 Thunk 函数,方法如下:
关于 Thunk 函数, 可以直接使用 thunkify 模块:
使用格式和上面的
结合 Thunk 函数和协程,我们可以实现自动流程管理。之前我们使用 Generator 时候使用
如果说 Thunk 可以有现成的库使用,那么这个自动执行的 Generator 函数也有现成的库可以使用——co模块(https://github.com/tj/co)。用法与上面类似,不过 co 模块返回一个 Promise 对象。使用方式如下:
这里需要注意的是:yield 后面只能跟一个 thunk 函数或 promise 对象。上例中第8行 yield 后面的 readFile 是一个 thunk 函数,所以可以使用。
上面已经讲解了 thunk 函数实现自动流程管理,下面使用 Promise 实现一下:
我们把它改写成 async 函数:
async 函数对 Generator 函数做了一下改进:
Generator 函数需要手动通过返回值的 next 方法执行,而 async 函数自带执行器,执行方式和普通函数完全一样。
语义明确,async 表示异步,await 表示后续表达式需要等待触发的异步操作结束
co 模块中 yield 后面只能跟一个 thunk 函数或 promise 对象,而 await 后面可以是任何类型(不是 Promise 对象就同步执行)
返回值是一个 Promise 对象,不是 Iterator ,比 Generator 方便
我们可以实现这样的一个 async 函数:
我们使用 async 函数做点简单的事情:
这里需要注意:应该把后面跟 promise对象的 await 放在一个 try 中,防止其被 rejected。当然上面的 try 语句也可以这样写:
对于函数参数中的回调函数不建议使用,避免出现不应该的错误
在一个 DOM 元素上绑定一系列动画,每一个动画完成才开始下一个,如果某个动画执行失败,返回最后一个执行成功的动画的返回值
Promise 方法
Generator 方法
async 函数方法
ECMAScript6 (2):解构赋值
ECMAScript6 (3):数值类型扩展
ECMAScript6 (4):字符串类型扩展
ECMAScript6 (5):函数的扩展
ECMAScript6 (6):数组的扩展
ECMAScript6 (7):二进制数组
ECMAScript6 (8):对象的扩展
ECMAScript6 (9):正则表达式的扩展
ECMAScript6 (10):Symbol基本类型
ECMAScript6 (11):Set 与 Map
ECMAScript6 (12):Proxy 和 Reflect
ECMAScript6 (13):Generator 函数
ECMAScript6 (14):iterator 迭代器
ECMAScript6 (15):Promise 对象
异步编程
程序执行分为同步和异步,如果程序每执行一步都需要等待上一步完成才能开始,此所谓同步。如果程序在执行一段代码的同时可以去执行另一段代码,等到这段代码执行完毕再吧结果交给另一段代码,此所谓异步。比如我们需要请求一个网络资源,由于网速比较慢,同步编程就意味着用户必须等待下载处理结束才能继续操作,所以用户体验极为不好;如果采用异步,下载进行中用户继续操作,当下载结束了,告诉用户下载的数据,这样体检就提升了很多。因此异步编程十分重要。
从计算机的角度来讲,js 只有一个线程,如果没有异步编程那一定会卡死的!异步编程主要包括以下几种:
回调函数
事件监听
发布/订阅模型
Promise对象
ES6异步编程
回调函数 和 Promise
回调函数应该是 js 中十分基础和简单的部分,我们在定义事件,在计时器等等使用过程中都使用过:fs.readFile('/etc/passwd', function(err, data){ if(err) throw err; console.log(data); });
比如这里的这个文件读取,定义了一个回调函数,在读取文件成功或失败是调用,并不会立刻调用。
如同之前在 Promise 中提到的,当我想不断的读入多个文件,就会遇到回调函数嵌套,书写代码及其的不方便,我们称之为”回调地狱”。因此 ES6 中引入是了 Promise 解决这个问题。具体表现参看之前的 Promise 部分。但是 Promise 也带来了新的问题,就是代码冗余很严重,一大堆的 then 使得回调的语义不明确。
协程
所谓协程就是几个程序交替执行:A开始执行,执行一段时间后 B 执行,执行一段时间后再 A 继续执行,如此反复。function* asyncJob(){ //... var f = yield readFile(fileA); //... }
通过一个 Generator 函数的 yield, 可以将一个协程中断,去执行另一个协程。我们可以换一个角度理解 Generator 函数:它是协程在 ES6 中的具体体现。我们可以简单写一个异步任务的封装:
var fetch = require('node-fetch'); function* gen(){ var url = 'http://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio); } var g = gen(); var result = g.next(); //返回的 value 是一个 Promise 对象 result.value.then(function(data){ return data.json; }).then(function(data){ g.next(data); });
Thunk 函数
在函数传参数时我们考虑这样一个问题:function fun(x){ return x + 5; } var a = 10; fun(a + 10);
这个函数返回25肯定没错,但是,我们传给函数 fun 的参数在编译时到底保留
a + 10还是直接传入
20?显然前者没有事先计算,如果函数内多次使用这个参数,就会产生多次计算,影响性能;而后者事先计算了,但如果函数里不使用这个变量就白浪费了性能。采用把参数原封不动的放入一个函数(我们将这个函数称为 Thunk 函数),用的使用调用该函数的方式。也就是上面的前一种方式传值。所以上面代码等价于:
function fun(x){ return x() + 5; } var a = 10; var thunk = function(){ return a + 10}; fun(thunk);
**但是 js 不是这样的!**js 会把多参数函数给 Thunk 了,以减少参数:
var fs = require('fs'); fs.readFile(fileName, callback); var readFileThunk = Thunk(fileName); readFileThunk(callback); var Thunk = function(fileName){ return function(callback){ return fs.readFile(fileName,callback); }; };
这里任何具有回调函数的函数都可以写成这样的 Thunk 函数,方法如下:
function Thunk(fn){ return function(){ var args = Array.prototype.slice.call(arguments); return function (callback){ args.push(callback); return fn.apply(this, args); } } } //这样fs.readFile(fileName, callback); 写作如下形式 Thunk(fs.readFile)(fileName)(callback);
关于 Thunk 函数, 可以直接使用 thunkify 模块:
npm install thunkify
使用格式和上面的
Thunk(fs.readFile)(fileName)(callback);一致,但使用过程中需要注意,其内部加入了检查机制,只允许 callback 被回调一次!
结合 Thunk 函数和协程,我们可以实现自动流程管理。之前我们使用 Generator 时候使用
yield关键字将 cpu 资源释放,执行移出 Generator 函数。可以怎么移回来呢?之前我们手动调用 Generator 返回的迭代器的 next() 方法,可这毕竟是手动的,现在我们就利用 Thunk 函数实现一个自动的:
var fs = require('fs'); var thunkify = require('thunkify'); var readFile = thunkify(fs.readFile); var gen = function*(...args){ //args 是文件路径数组 for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } }; (function run(fn){ var gen = fn(); function next(err, data){ if(err) throw err; var result = gen.next(data); if(result.done) return; //递归直到所以文件读取完成 result.value(next); //递归执行 } next(); })(gen); //之后可以使用 run 函数继续读取其他文件操作
如果说 Thunk 可以有现成的库使用,那么这个自动执行的 Generator 函数也有现成的库可以使用——co模块(https://github.com/tj/co)。用法与上面类似,不过 co 模块返回一个 Promise 对象。使用方式如下:
var co = require('co'); var fs = require('fs'); var thunkify = require('thunkify'); var readFile = thunkify(fs.readFile); var gen = function*(...args){ //args 是文件路径数组 for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } }; co(gen).then(function(){ console.log("files loaded"); }).catch(function(err){ console.log("load fail"); });
这里需要注意的是:yield 后面只能跟一个 thunk 函数或 promise 对象。上例中第8行 yield 后面的 readFile 是一个 thunk 函数,所以可以使用。
上面已经讲解了 thunk 函数实现自动流程管理,下面使用 Promise 实现一下:
var fs = require('fs'); var readFile = function(fileName){ return new Promise(function(resolve, reject){ fs.readFile(fileName, function(error,data){ if(error) reject(error); resolve(data); }); }); }; var gen = function*(){ for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } }; (function run(gen){ var g = gen(); var resolve = function(data){ var result = g.next(data); if(result.done) return result.value; result.value.then(resolve); } g.next().value.then(function(data){ resolve(data); }); resolve(); })(gen); //之后可以使用 run 函数继续读取其他文件操作
async 函数
ES7 中提出了 async 函数,但是现在已经可以用了!可这个又是什么呢?其实就是 Generator 函数的改进,我们上文写过一个这样的 Generator 函数:var gen = function*(){ for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } };
我们把它改写成 async 函数:
var asyncReadFiles = async function(){ //* 替换为 async for(var i = 0, len = args.length; i < len; i++){ var r = await readFile(args[i]); //yield 替换为 await console.log(r.toString()); } };
async 函数对 Generator 函数做了一下改进:
Generator 函数需要手动通过返回值的 next 方法执行,而 async 函数自带执行器,执行方式和普通函数完全一样。
var result = asyncReadFiles(fileA, fileB, fileC);
语义明确,async 表示异步,await 表示后续表达式需要等待触发的异步操作结束
co 模块中 yield 后面只能跟一个 thunk 函数或 promise 对象,而 await 后面可以是任何类型(不是 Promise 对象就同步执行)
返回值是一个 Promise 对象,不是 Iterator ,比 Generator 方便
我们可以实现这样的一个 async 函数:
async function asyncFun(){ //code here } //equal to... function asyncFun(args){ return fun(function*(){ //code here... }); function fun(genF){ return new Promise(function(resolve, reject){ var gen = genF(); function step(nextF){ try{ var next = nextF(); } catch(e) { return reject(e); } if(next.done){ return resolve(next.value); } Promise.resolve(next.value).then(function(data){ step(function(){ return gen.next(data); }); }, function(e){ step(function(){ return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); } }
我们使用 async 函数做点简单的事情:
function timeout(ms){ return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function delay(nap, ...values){ while(1){ try{ await timeout(nap); } catch(e) { console.log(e); } var val = values.shift(); if(val) console.log(val) else break; } } delay(600,1,2,3,4); //每隔 600ms 输出一个数
这里需要注意:应该把后面跟 promise对象的 await 放在一个 try 中,防止其被 rejected。当然上面的 try 语句也可以这样写:
var ms = await timeout(nap).catch((e) => console.log(e));
对于函数参数中的回调函数不建议使用,避免出现不应该的错误
//反例: 会得到错误结果 async function fun(db){ let docs = [{},{},{}]; docs.forEach(async function(doc){ //ReferenceError: Invalid left-hand side in assignment await db.post(doc); }); } //改写, 但依然顺序执行 async function fun(db){ let docs = [{},{},{}]; for(let doc of docs){ await db.post(doc); } } //改写, 并发执行 async function fun(db){ let docs = [{},{},{}]; let promises = docs.map((doc) => db.post(doc)); let result = await Promise.all(promises) console.log(result); } //改写, 并发执行 async function fun(db){ let docs = [{},{},{}]; let promises = docs.map((doc) => db.post(doc)); let result = []; for(let promise of promises){ result.push(await promise); } console.log(result); }
Promise,Generator 和 async 函数比较
这里我们实现一个简单的功能,可以直观的比较一下。实现如下功能:在一个 DOM 元素上绑定一系列动画,每一个动画完成才开始下一个,如果某个动画执行失败,返回最后一个执行成功的动画的返回值
Promise 方法
function chainAnimationPromise(ele, animations){ var ret = null; //存放上一个动画的返回值 var p = Promise.resolve(); for(let anim of animations){ p = p.then(function(val){ ret = val; return anim(ele); }); } return p.catch(function(e){ /*忽略错误*/ }).then(function(){ return ret; //返回最后一个执行成功的动画的返回值 }); }
Generator 方法
function chainAnimationGenerator(ele, animations){ return fun(function*(){ var ret = null; try{ for(let anim of animations){ ret = yield anim(ele); } } catch(e) { /*忽略错误*/ } return ret; }); function fun(genF){ return new Promise(function(resolve, reject){ var gen = genF(); function step(nextF){ try{ var next = nextF(); } catch(e) { return reject(e); } if(next.done){ return resolve(next.value); } Promise.resolve(next.value).then(function(data){ step(function(){ return gen.next(data); }); }, function(e){ step(function(){ return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); } }
async 函数方法
async function chainAnimationAsync(ele, animations){ var ret = null; try{ for(let anim of animations){ ret = await anim(elem); } } catch(e){ /*忽略错误*/ } return ret; }
一个经典题
console.log(0); setTimeout(function(){ console.log(1) },0); setTimeout(function(){ console.log(2); },1000); var pro = new Promise(function(resolve, reject){ console.log(3); resolve(); }).then(resolve => console.log(4)); console.log(5); setTimeout(function(){ console.log(6) },0); pro.then(resolve => console.log(7)); var pro2 = new Promise(function(resolve, reject){ console.log(8); resolve(10); }).then(resolve => console.log(11)) .then(resolve => console.log(12)) .then(resolve => console.log(13)); console.log(14); // 0 3 5 8 14 4 11 7 12 13 1 6 2
相关文章推荐
- 孙鑫教学视频笔记 (16)线程同步与异步套接字编程
- 孙鑫MFC笔记教程(16)--线程同步和异步套接字编程
- LinuxC/C++编程基础(16) boost异步socket处理
- 孙鑫教学视频笔记 (16)线程同步与异步套接字编程
- Web 2.0 编程思想:16 条法则
- C++ boost::asio编程-异步TCP详解及实例代码
- Socket编程 (异步通讯,解决Udp丢包) - Part4
- boost asio异步读写网络编程实例详解
- 深入浅出Linux设备驱动编程--设备驱动中的异步通知
- Dynamics CRM 2011 编程系列(16):插件
- socket编程的同步、异步与阻塞、非阻塞示例详解之二
- iOS之网络编程(POST异步)
- 异步套接字编程之WSAAsyncSelect模型
- 16、网络编程
- C#异步编程模式IAsyncResult概述
- 【网络编程】半同步--半异步线程池源码分析之线程池(基于C++11)
- 续实例解析SOCKET编程模型之异步通信篇-客户端
- WinInet 编程中如何使用异步
- 网络编程释疑之:同步,异步,阻塞,非阻塞
- 翻译:使用Libevent的快速可移植非阻塞网络编程:异步IO简介 (一)