内容简介:前言:大家好,我叫邵威儒,大家都喜欢喊我小邵,学的金融专业却凭借兴趣爱好入了程序猿的坑,后续我会陆陆续续更新javascript方面,尽量把javascript这个学习路径体系都写一下包括前端所常用的es6、angular、react、vue、nodejs、koa、express、公众号等等
前言:大家好,我叫邵威儒,大家都喜欢喊我小邵,学的金融专业却凭借兴趣爱好入了程序猿的坑, 从大学买的第一本vb.net和自学vb.net ,我就与编程结下不解之缘,随后自学易语言写游戏辅助、交易软件,至今进入了前端领域,看到不少朋友都写文章分享,自己也弄一个玩玩,以下文章纯属个人理解,便于记录学习,肯定有理解错误或理解不到位的地方,意在站在前辈的肩膀,分享个人对技术的通俗理解,共同成长!
后续我会陆陆续续更新javascript方面,尽量把javascript这个学习路径体系都写一下
包括前端所常用的es6、angular、react、vue、nodejs、koa、express、公众号等等
都会从浅到深,从入门开始逐步写,希望能让大家有所收获,也希望大家关注我~
源码地址: github.com/iamswr/prom…
在看这篇文章之前可以先看看整个异步的发展 juejin.im/post/5b6e5c…
主要讲一下Generator和co库、async await,配合使用
Generator
Generator 基本使用
Generator是一个生成器,生成出一个迭代器,主要是用来控制异步流程的,目前在现有的库中还是比较少看到generator的,目前主要使用generator的是redux-saga这个库,koa1.0也是用generator,但是现在都改为async/await。
generator生成器看起来很像普通函数,但是它是在函数名前加了一个 * 号
function* say(){ // 在函数名前加 * } 复制代码
生成器函数可以暂停,而普通函数则是默认一路到底执行代码,生成器函数在内部碰到yield就可以实现暂停功能,使用next进行迭代
function* say(){ yield 1 yield 2 yield 3 return 'end' } // 1.执行这个生成器看起来跟执行普通函数差不多, // 但是实际上,执行这个生成器,会返回一个迭代器 let it = say() // 2.此时it是一个迭代器 iterator,打印输出是 {} console.log(it) let obj1 = it.next() // 3. 使用next进行迭代,打印输出 { value: 1, done: false } // 可以看出,我们执行say生成器,用it来接收生成器返回的迭代器, // 而通过迭代器it执行next(),会返回 { value: 1, done: false } // 这里的value代表的是上面yield后的值,依次返回, // done为false,意思是还没结束,后面还有yield或者return,当走到return时 // done会变为true,也就是完成了 console.log(obj1) 复制代码
我们这样完整看一下
function* say() { let a = yield 1 // 第一个it.next()时返回 let b = yield 2 // 第二个it.next()时返回 let c = yield 3 // 第三个it.next()时返回 return 'end' // 第四个it.next()时返回 } let it = say() let obj1 = it.next() console.log(obj1) // { value: 1, done: false } let obj2 = it.next() console.log(obj2) // { value: 2, done: false } let obj3 = it.next() console.log(obj3) // { value: 3, done: false } let obj4 = it.next() console.log(obj4) // { value: 'end', done: true } 复制代码
迭代器,要我们自行一个一个去迭代,一般我们会通过下面这样进行迭代
function* say() { let a = yield 1 // 第一个it.next()时返回 let b = yield 2 // 第二个it.next()时返回 let c = yield 3 // 第三个it.next()时返回 return 'end' // 第四个it.next()时返回 } let it = say() function next(){ let { value,done } = it.next() console.log(value) // 依次打印输出 1 2 3 end if(!done) next() // 直到迭代完成 } next() 复制代码
通过上面的例子我们大概明白generator大概是怎么执行的了,
那么下面我们讲讲,怎么往generator里面放东西。
function* say() { let a = yield 'hello swr1' console.log(a) let b = yield 'hello swr2' console.log(b) } let it = say() // 返回迭代器 // 打印输出 { value: 'hello swr1', done: false } // 此时执行迭代器的第一个next,会把上图红色圈的区域执行,并且输出'hello swr1' // 此时需要注意的是let a = yield 'hello swr1',并非是把yield 'hello swr1' // 赋值给a,那么a是什么时候被赋值呢?我们接着看下面 console.log(it.next()) // 打印输出 我是被传进来的1 // { value: 'hello swr2', done: false } // 此时我们在next里传参,实际上就是当执行第二个next的时候, // 会把上面蓝色圈的区域执行,而这个next的参数, // 会被赋值给a,然后执行console.log(a),然后把'hello swr2'输出 console.log(it.next('我是被传进来的1')) // 打印输出 我是被传进来的2 // { value: undefined, done: true } // 此时我们第三次执行next,实际上就是当执行了第三个next的时候, // 会把上面黄色圈的区域执行,而这个next的参数, // 会被赋值给b,然后执行console.log(b),然后因为没有显式写return xxx, // 会被默认返回undefined console.log(it.next('我是被传进来的2')) 复制代码
写到这里,我和大家一样不解,这东西到底有什么用,而且这样一个一个next迭代,也很繁琐,下面就来点实用的,generator可以和promise配合使用。
generator和Promise、co配合使用
在讲generator和Promise、co配合使用之前我会讲一下promise化的函数怎么写,因为我们日常开发当中,经常会使用到promise,用一次,就写一大堆代码也不好,那么我们会考虑到写一个通用的函数,可以把回调函数方式的函数,改为promise
// 假设我们有3个文件,1.txt对应的内容为文本'2.txt' 2.txt对应的内容为文本'3.txt' 3.txt对应的内容为文本'hello swr' let fs = require('fs') // promise化函数 function promisify(fn){ return function(...args){ return new Promise((resolve,reject)=>{ fn(...args,(err,data)=>{ // node的api第一个参数为err if(err) reject(err) resolve(data) }) }) } } // 把fs.readFile函数promise化 let read = promisify(fs.readFile) read('1.txt','utf8').then((data)=>{ console.log(data) // 打印输出为 '2.txt' }) 复制代码
这样我们就完成了一个通用的promise化函数。
接下来我们要去了解一下co库
co库地址: github.com/tj/co
$ npm install co
// 本处代码promisify源用上面的函数promisify // 本处代码read源用上面的函数read let co = require('co') function* r(){ let r1 = yield read('1.txt','utf8') let r2 = yield read(r1,'utf8') let r3 = yield read(r2,'utf8') return r3 } // 此时我们想取到r3,也就是3.txt里的内容'hello swr' // 方法一: let it = r() let { value,done } = it.next() // value为一个promise对象 // 该对象会把resolve的值传给下一个then value.then((data)=>{ // data值为'2.txt' let { value,done } = it.next(data) return value }).then((data)=>{ // data值为'3.txt' let { value,done } = it.next(data) return value }).then((data)=>{ // data值为'hello swr' console.log(data) // 打印输出 'hello swr' }) // 这样的写法,反而显得很繁琐复杂了,那么我们下面看下使用generator+co是怎么使用的 // 方法二: co(r()).then(data=>{ console.log(data) // 打印输出 'hello swr' }) // 是不是发现generator+co非常高效? // 代码更像同步代码了,那么接下来我们自己实现一个co~ 复制代码
co是如何实现呢?
function co(it){ return new Promise((resolve,reject)=>{ function next(data){ let { value,done } = it.next(data) if(!done){ value.then((data)=>{ next(data) },reject) }else{ resolve(value) } } next() }) } 复制代码
当有两个gennerator函数时,并且其中一个嵌套另外一个
function* a(){ yield 1 } function* b(){ // 1. 当我们想在generator b中嵌套generator a时,怎么嵌套呢? // 2. yield *a(); ==> yield 1,实际上就是把yield 1 放在这个位置 // 在生成器函数中使用生成器函数 需要使用 * yield *a() yield 2 } let it = b() console.log(it.next()) 复制代码
async await
// 假设我们有3个文件,1.txt对应的内容为文本'2.txt' 2.txt对应的内容为文本'3.txt' 3.txt对应的内容为文本'hello swr' async function r(){ try{ let r1 = await read('1.txt','utf8') let r2 = await read(r1,'utf8') let r3 = await read(r2,'utf8') return r3 }catch(e){ console.log(e) } } r().then((data)=>{ console.log(data) // hello swr }) 复制代码
有没发现,async + await = generator + co?
async await解决了异步问题
- 可以让代码像同步
- 可以使用try catch
- 可以使用promise
- 如果let r1 = await 后面等待的是promise,那么会把promise的结果赋值给前面的r1,如果let r1 = await 后面等待的是普通值,那么就会把这个普通值赋值给前面的r1
// 那么我想把上面的3个请求改为并发的呢? let arr = Promise.all([read('1.txt','utf8'),read('2.txt','utf8'),read('3.txt','utf8')]) 复制代码
那么async await通过babel编译后是怎样的呢?
'use strict'; var r = function () { var _ref = _asyncToGenerator( // async await被编译成generator /*#__PURE__*/regeneratorRuntime.mark(function _callee() { var r1, r2, r3; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.prev = 0; _context.next = 3; return read('100.txt', 'utf8'); case 3: r1 = _context.sent; _context.next = 6; return read(r1, 'utf8'); case 6: r2 = _context.sent; _context.next = 9; return read(r2, 'utf8'); case 9: r3 = _context.sent; return _context.abrupt('return', r3); case 13: _context.prev = 13; _context.t0 = _context['catch'](0); console.log(_context.t0); case 16: case 'end': return _context.stop(); } } }, _callee, this, [[0, 13]]); })); return function r() { return _ref.apply(this, arguments); }; }(); function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { // 相当于我们上门写的next函数 try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 复制代码
我们从callback promise generator async + await基本了解了异步发展的进程了, 接下来我们用一个例子来贯穿这几个~
我们有个需求,分别有3个球,一个球执行完动画,才会轮到下一个球执行动画,以此类推
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> .ball { position: absolute; width: 100px; height: 100px; background: red; border-radius: 50%; left: 0; } div:nth-child(1) { top: 0px } div:nth-child(2) { top: 120px } div:nth-child(3) { top: 240px } </style> </head> <body> <div> <div class="ball"></div> <div class="ball"></div> <div class="ball"></div> </div> <script> // 4.------------------// async await let balls = document.querySelectorAll('.ball'); function move(ele, distance) { return new Promise((resolve, reject) => { let movex = 0; let interval = setInterval(() => { movex++; ele.style.left = movex + 'px'; if (movex >= distance) { resolve(); clearInterval(interval); } }, 6) }) } async function m() { await move(balls[0], 500); await move(balls[1], 400); await move(balls[2], 300) } m().then(data=>{ alert('ok'); }); // 3.------------------// generator + co // let balls = document.querySelectorAll('.ball'); // function move(ele, distance) { // return new Promise((resolve, reject) => { // let movex = 0; // let interval = setInterval(() => { // movex++; // ele.style.left = movex + 'px'; // if (movex >= distance) { // resolve(); // clearInterval(interval); // } // }, 6) // }) // } // function* m() { // yield move(balls[0], 500); // yield move(balls[1], 400); // yield move(balls[2], 300) // } // function co(it) { // return new Promise((resolve, reject) => { // function next(data) { // let { value, done } = it.next(data); // if (!done) { // value.then(data => { // next(data); // }, reject); // }else{ // resolve(value); // } // } // next(); // }) // } // co(m()).then(data => { // alert('done') // }) // 2.------------------// promise // let balls = document.querySelectorAll('.ball'); // function move(ele, distance) { // return new Promise((resolve, reject) => { // let movex = 0; // let interval = setInterval(() => { // movex++; // ele.style.left = movex + 'px'; // if (movex >= distance) { // resolve(); // clearInterval(interval); // } // }, 6) // }) // } // move(balls[0],500).then(data=>{ // return move(balls[1],400) // }).then(data=>{ // return move(balls[2],300) // }).then(data=>{ // alert('ok'); // }) // 1.------------------// callback // let balls = document.querySelectorAll('.ball'); // function move(ele, distance, cb) { // let movex = 0; // let interval = setInterval(()=>{ // movex++; // ele.style.left = movex+'px'; // if(movex >= distance){ // cb(); // clearInterval(interval); // } // },6) // } // move(balls[0], 500, function () { // move(balls[1], 400, function () { // move(balls[2], 300, function () { // alert('成功') // }) // }) // }) </script> </body> </html> 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入理解程序设计
[美] Jonathan Bartlett / 郭晴霞 / 人民邮电出版社 / 2014-1 / 49.00
是否真正理解汇编语言,常常是普通程序员和优秀程序员的分水岭。《深入理解程序设计:使用Linux汇编语言》介绍了Linux平台下的汇编语言编程,教你从计算机的角度看问题,从而了解汇编语言及计算机的工作方式,为成就自己的优秀程序员之梦夯实基础。 很多人都认为汇编语言晦涩难懂,但New Medio技术总监Jonathan Bartlett的这本书将改变人们的看法。本书首先介绍计算机的体系结构,然后......一起来看看 《深入理解程序设计》 这本书的介绍吧!