关于generator:安装snmp-generator遇到usrbinld-final-link-failed问题

在github下载代码后 做go build 呈现报错 /usr/bin/ld: xxx : unrecognized relocation (0x2a) in section `.text`/usr/bin/ld: final link failed: 谬误的值# 查看旧版本[root@prometheus-primary1 generator]# ld -v# 以我的为例,以后零碎中 ld 的版本为GNU ld version 2.25.1-31.base.el7ld 工具的版本后, 起因是版本较低更新成 2.26.1 版本[root@prometheus-primary1 generator]# wget http://ftp.gnu.org/gnu/binutils/binutils-2.26.1.tar.gz# 解压[root@prometheus-primary1 generator]# tar -xf binutils-2.26.1.tar.gz [root@prometheus-primary1 generator]# cd ./binutils-2.26.1# 通过 configure 生成 makefile 文件,以及设置 make install 时的装置门路[root@prometheus-primary1 generator]# ./configure --prefix=/home/binutils-2.26.1/build# 编译[root@prometheus-primary1 generator]# make -j# 编译生成文件[root@prometheus-primary1 generator]# make install# 配置零碎环境变量[root@prometheus-primary1 generator]# vim /etc/profile 追加 export PATH=... 也能够 # 这样能够确保机器重启后,导入的环境变量不会被重置echo "export PATH=/home/binutils-2.26.1/build/bin:$PATH" >> /etc/profile.d/localld.shsource /etc/profile.d/localld.sh# test[root@prometheus-primary1 generator]# ld -vGNU ld (GNU Binutils) 2.26.1而后持续测试 go build && make mibs ...

June 11, 2022 · 1 min · jiezi

关于generator:理解-generator

1. 生成器中while设置为trueredux-saga 文档中提到take实现takeEvery在while中应用了yield监听将来的 action 循环有限次 function* watchRequests() { console.info("xx"); let preVal = 0; while (true) { console.info("yy"); const ret = yield preVal + 2; // if (ret === 3) { // break; // } preVal = ret; console.info("output:", ret); }}const it = watchRequests();for (const item of it) { console.info(item);}这么调用,生成器中的while无终止条件,会导致有限循环。 循环无限次数 function* watchRequests() { console.info("xx"); let preVal = 0;//记录上次内部传进来的值 while (true) { console.info("yy"); const ret = yield preVal + 2; // if (ret === 3) { // break; // } preVal = ret; console.info("output:", ret); }}const it = watchRequests();// for (const item of it) {// console.info(item);// }for (let i = 0; i < 5; i++) { const r = it.next(i); console.info("r:", r);}输入: ...

September 14, 2020 · 3 min · jiezi

JavaScript异步编程

前言从我们一开始学习JavaScript的时候就听到过一段话:JS是单线程的,天生异步,适合IO密集型,不适合CPU密集型。但是,多数JavaScript开发者从来没有认真思考过自己程序中的异步到底是怎么出现的,以及为什么会出现,也没有探索过处理异步的其他方法。到目前为止,还有很多人坚持认为回调函数就完全够用了。 但是,随着JavaScript面临的需求越来越多,它可以运行在浏览器、服务器、甚至是嵌入式设备上,为了满足这些需求,JavaScript的规模和复杂性也在持续增长,使用回调函数来管理异步也越来越让人痛苦,这一切,都需要更强大、更合理的异步方法,通过这篇文章,我想对目前已有JavaScript异步的处理方式做一个总结,同时试着去解释为什么会出现这些技术,让大家对JavaScript异步编程有一个更宏观的理解,让知识变得更体系化一些。 正文Step1 - 回调函数回调函数大家肯定都不陌生,从我们写一段最简单的定时器开始: setTimeout(function () { console.log('Time out');}, 1000);定时器里面的匿名函数就是一个回调函数,因为在JS中函数是一等公民,所以它可以像其他变量一样作为参数进行传递。这样看来,通过回调函数来处理异步挺好的,写着也顺手,为什么要用别的方法呢? 我们来看这样一个需求: 上面是微信小程序的登录时序图,我们的需求和它类似但又有些差别,想要获取一段业务数据,整个过程分为3步: 调用秘钥接口,获取key携带key调用登录接口,获取token和userId携带token和userId调用业务接口,获取数据可能上述步骤和实际业务中的有些出入,但是却可以用来说明问题,请大家谅解。 我们写一段代码来实现上述需求: let key, token, userId;$.ajax({ type: 'get', url: 'http://localhost:3000/apiKey', success: function (data) { key = data; $.ajax({ type: 'get', url: 'http://localhost:3000/getToken', data: { key: key }, success: function (data) { token = data.token; userId = data.userId; $.ajax({ type: 'get', url: 'http://localhost:3000/getData', data: { token: token, userId: userId }, success: function (data) { console.log('业务数据:', data); }, error: function (err) { console.log(err); } }); }, error: function (err) { console.log(err); } }); }, error: function (err) { console.log(err); }});可以看到,整段代码充满了回调嵌套,代码不仅在纵向扩展,横向也在扩展。我相信,对于任何人来说,调试起来都会很困难,我们不得不从一个函数跳到下一个,再跳到下一个,在整个代码中跳来跳去以查看流程,而最终的结果藏在整段代码的中间位置。真实的JavaScript程序代码可能要混乱的多,使得这种追踪难度会成倍增加。这就是我们常说的回调地狱(Callback Hell)。 ...

July 2, 2019 · 4 min · jiezi

总结异步编程的六种方式

异步编程众所周知 JavaScript 是单线程工作,也就是只有一个脚本执行完成后才能执行下一个脚本,两个脚本不能同时执行,如果某个脚本耗时很长,后面的脚本都必须排队等着,会拖延整个程序的执行。那么如何让程序像人类一样可以多线程工作呢?以下为几种异步编程方式的总结,希望与君共勉。 回调函数事件监听发布订阅模式PromiseGenerator (ES6)async (ES7)异步编程传统的解决方案:回调函数和事件监听 初始示例:假设有两个函数, f1 和 f2,f1 是一个需要一定时间的函数。 function f1() { setTimeout(function(){ console.log('先执行 f1') },1000)}function f2() { console.log('再执行 f2')}回调函数因为 f1 是一个需要一定时间的函数,所以可以将 f2 写成 f1 的回调函数,将同步操作变成异步操作,f1 不会阻塞程序的运行,f2 也无需空空等待,例如 JQuery 的 ajax。 回调函数的demo: function f1(f2){ setTimeout(function(){ console.log('先执行 f1') },1000) f2()}function f2() { console.log('再执行 f2')}效果如下: 总结:回调函数易于实现、便于理解,但是多次回调会导致代码高度耦合 事件监听脚本的执行不取决代码的顺序,而取决于某一个事件是否发生。 事件监听的demo $(document).ready(function(){ console.log('DOM 已经 ready')});发布订阅模式发布/订阅模式是利用一个消息中心,发布者发布一个消息给消息中心,订阅者从消息中心订阅该消息,。类似于 vue 的父子组件之间的传值。 发布订阅模式的 demo //订阅done事件$('#app').on('done',function(data){ console.log(data)})//发布事件$('#app').trigger('done,'haha')PromisePromise 实际就是一个对象, 从它可以获得异步操作的消息,Promise 对象有三种状态,pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise 的状态一旦改变之后,就不会在发生任何变化,将回调函数变成了链式调用。 Promise 封装异步请求demo export default function getMethods (url){ return new Promise(function(resolve, reject){ axios.get(url).then(res => { resolve(res) }).catch(err =>{ reject(err) }) })}getMethods('/api/xxx').then(res => { console.log(res)}, err => { console.log(err)})GeneratorGenerator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,使用该对象的 next() 方法,可以遍历 Generator 函数内部的每一个状态,直到 return 语句。 ...

May 15, 2019 · 1 min · jiezi

生成器与迭代器

之前的文章 写到了 Generator 与异步编程的关系,其实简化异步编程只是 Generator 的“副业”,Generator 本身却不是为异步编程而存在。 生成器函数我们看 Generator 自身的含义——生成器,就是产生序列用的。比如有如下函数: function* range(start, stop) { for (let item = start; item < stop; ++item) { yield item; }}range 就是一个生成器函数,它自身是函数可以调用(typeof range === 'function' // true),但又与普通函数不同,生成器函数(GeneratorFunction)永远返回一个生成器(Generator) 注:我们通常所说的 Generator 实际上指生成器函数(GeneratorFunction),而把生成器函数返回的对象称作迭代器(Iterator)。由于感觉“生成器函数”返回“生成器”这句话有些拗口,下文沿用生成器和迭代器的说法。 迭代器初次调用生成器实际上不执行生成器函数的函数体,它只是返回一个迭代器,当用户调用迭代器的 next 函数时,程序才开始真正执行生成器的函数体。当程序运行到 yield 表达式时,会将 yield 后面表达式的值作为 next 函数的返回值(的一部分)返回,函数本身暂停执行。 const iterator = range(0, 10); // 获取迭代器const value1 = iterator.next().value; // 获取第一个值 => 0const value2 = iterator.next().value; // 获取第二个值 => 1next 返回值是一个对象,包含两个属性 value 和 done。value 即为 yield 后面表达式的值,done 表示函数是否已经结束(return)。如果函数 return(或执行到函数尾退出,相当于 return undefined),则 done 为 true,value 为 return 的值。 ...

April 22, 2019 · 2 min · jiezi

前端异步解决方案-4.2(generator+promise)

为什么要实现generator和promise的结合呢?大部分网上的文章都说是为了更简单的处理错误,不过这个我暂时还没有感悟,我反而觉得其实差不多;但是还是先学习一下用法吧;先从简单的用法讲起: //最简单的用法function request() { return new Promise(function (resolve, reject) { setTimeout(function () { let num = Math.random(); if (num > 0.9) { reject(num + "request err") } else { resolve(num * 100); } }, 100) })}let it = gen();let p = it.next().value;// 在yield处被返回的Promise 被赋给了 pp.then(res => it.next(res).value, err => it.throw(err));// 发生错误时,将错误抛入生成器(gen)中function* gen() { try { let response = yield request(); console.log(response.text); } catch (error) { console.log('Ooops, ', error.message); // 可以捕获Promise抛进来的错误!}} }}这里request的写法就是普通的Promise异步的写法,而gen中异步的写法就已经非常像同步了,唯一一个缺点就是中间这一段代码在我们多次异步的时候,我们需要不断的加长就像这样 ...

April 21, 2019 · 2 min · jiezi

「 JS 」快速上手异步方案

问题:解决异步回调的深层嵌套的问题.(回调地狱)1. Promisepromise对象用于表示一个异步操作的最终状态,promise在回调代码和将要执行这个任务的异步代码之间提供了一种可靠的中间机制来管理回调。//构造函数,回调函数是同步的回调new Promise(function(resolve,reject){ ….//异步操作})Promise的实例对象有三个状态 pending: 初始状态,既不是成功,也不是失败状态。fulfilled: 意味着操作成功完成。rejected: 意味着操作失败resolve和reject分别是两个函数,当在回调中调用时,会改变promise实例的状态,resolve改变状态为成功,reject为失败.thenPromise.prototype.then()当promise对象的状态发生改变时,绑定在其身上的then方法就会被调用。then方法包含两个参数:onfulfilled函数 和 onrejected函数,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争,then() 方法返回一个 Promise对象.返回值then方法返回一个新的Promise,而它的行为与then中的回调函数的返回值有关:如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。catchcatch() 方法返回一个Promise,并且处理拒绝的情况。Promise.prototype.catch()事实上,catch方法相当于then方法的第二个参数方法,触发拒绝状态.注意,如果调用 then的 Promise 的状态(fulfillment 或 rejection)发生改变,但是 then 中并没有关于这种状态的回调函数,那么 then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。所以在链式上,最终会执行到catch上.//链式示例new Promise(function (resolve, reject) {setTimeout(function () {console.log(“1”);resolve();}, 1000);}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(“2”);// resolve();reject();}, 1000);});}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(“3”);resolve();}, 1000);});}).then(function () {return new Promise(function (resolve, reject) {setTimeout(function () {console.log(“4”);resolve();}, 1000);});}).catch(function(){console.log(“catch”);})2. genaratorsymbol新的一种基础数据类型symbol,表示独一无二的值.它通常作为对象的属性键,内置对象普遍存在该值.// 一般用法,它并不是构造器,不能通过new,会报错.let sym = Symbol();// 在对象中表现形式,要用[]包裹,不然会被认为是string.var obj = { [Symbol()]:“value”;}该属性是匿名,所以不可枚举,只能通过.getOwnPropertySymbols()返回的数组.// 想要获得两个相同的Symbol,得通过.for()Symbol(“asd”) === Symbol(“asd”) // falseSymbol.for(“asd”) === Symbol.for(“asd”) // trueIterator迭代器,存在于特定几种可枚举的数据类型中.// 一般用以下这种形式的键保存了迭代器函数.// arr[Symbol.iterator]aarrSymbol.iterator.next( ) //遍历下一个,返回value和done,value表示值,done表示是否可以继续遍历下一个.//for…of循环遍历就是基于此,必须该数据类型有迭代器.回到generator//表现形式function* test(){ yield 1; //任务1 yield 2; //任务2 yield 3; //任务3 yield 4 ; //任务4}// 调用该方法会返回一个含有迭代对象的对象.var obj = text();obj.next(); //调用该方法时,每次到一个yield处停止.3. async/await作用:简化promise的使用编码, 不通过then()/catch()来指定回调函数以同步编码方式实现异步流程async function test (){ // 等待状态改变,自动执行到下一个wait处 var flag = await new Promise((resolve,reject)=>{ setTimeout(function(){ // 状态改变 resolve(data); //这里面的值传递给flag },1000) }) //通过flag传递数据 flag = await new Promise((resolve,reject)=>{ setTimeout(function(flag){ // 状态改变 resolve(flag); },1000,flag) })}test().catch(function(err){ //处理异常}); ...

April 18, 2019 · 1 min · jiezi

前端异步解决方案-4.1(generator+promise)

文章还未写完,发布只是为了看排版和图片效果前言终于开始写generator了,离这个系列的终结又进了一步。其实generator我还处在会用但是不理解原理的状态,但是知识不总结,不记录的话容易忘记,所以我还是把现在的一点心得记录下来。等到以后有了更深的理解再回来补充。想要看更深度解析generator的朋友可以移步漫话JavaScript与异步·第三话——Generator:化异步为同步这里面谈及了generator的底层实现及generator的用法。是我看过的文章中自认为解释的最好的一篇,而且篇幅也不长,建议大家去看一看。实现根据一贯的作风,我们先尝试自己实现generator尝试ing…………好了尝试完了,实现不了,老老实实的学习generator的用法吧。用法在我的理解中,generator最大的特点就是可以让函数在特定的地方停下,等待被唤醒后在函数内部环境中继续执行。我们结合代码来看一看:注释:【1】Iterator Object对象:参考 Iterator 文章比较长,但是如果只是想要了解什么是Iterator Object的话看完第一小节就足够了//输出分割线的函数,感兴趣的可以自行百度如何设置console.log的样式function cut_off(color) { console.log("%c——————————————",“color:"+color+";font-size:20px”);}//* 为generator函数的标识,如果我们想要创建一个generator函数就必须在function后面加上function generator() { let num1, num2; num1 = 123; console.log(“num1”, num1, “num2”, num2); //yield就是该函数内部暂停的地方,暂停的同时会把yield后面的值返回出去 yield num1; num2 = 456; console.log(“num1”, num1, “num2”, num2); yield num2; console.log(“num1”, num1, “num2”, num2); return “end”}console.log(“generator defined”);//函数返回一个Iterator Object对象;// 但是与普通函数不同的是,这个时候函数并不执行函数内部的代码let g = generator();console.log(“g defined”);cut_off(“red”);console.log(“g.next() run 1”);//开始执行函数内部的代码,并且遇在到yield的时候返回 yield后面的值console.log(g.next());cut_off(“red”);console.log(“g.next() run 2”);//从上次执行完的地方执行,并且遇在到yield的时候返回 yield后面的值console.log(g.next());cut_off(“red”);console.log(“g.next() run 3”);//从上次执行完的地方执行,这次是最后一次有值的返回,done的状态会变为trueconsole.log(g.next());cut_off(“red”);console.log(“g.next() run 4”);//已经执行完成之后再次被调用,永远返回{value:undefined, done: true}console.log(g.next());cut_off(“red”);console.log(“g.next() run 5”);//已经执行完成之后再次被调用,永远返回{value:undefined, done: true}console.log(g.next());贴上一张代码和运行结果的对比图

April 18, 2019 · 1 min · jiezi

JavaScript ES6相关的一些知识(/let、const/箭头函数/Promise/generate)

ES6是个啥ECMAScript是国际通过的标准化脚本语言JavaScript由ES,BOM,DOM组成ES是JavaScript的语言规范,同时JavaScript是ES的实现和扩展6就是JavaScript语言的下一代标准关于ES6的一些知识1.let、constES5中的作用域有:函数作用域,全局作用域ES6新增了块级作用域。由{}包括(if和for语句也属于块级作用域){ var a = 1; let b = 2; }console.log(a)//1console.log(b)//undefinedlet、const、var的区别var:可以跨块级作用域访问,不能跨函数作用域访问let:只能在块级作用域访问,不能跨函数使用const:定义常量,必须初始化且不能修改,只能在块级作用域内使用关于变量提升:var不论声明在何处都会莫默认提升到函数/全局最顶部,但是let和const不会进行变量提升2.arrow function 箭头函数箭头函数相当于匿名函数,简化了函数的定义定义就用=> 一个箭头箭头函数有两种格式:第一种:只包含一个表达式x=>x++相当于function(x)}{ return x++;}第二种:包含多条语句://包含判断等x=>{ if(x>0){ return x++; } else{ x–; }}//多个参数(x,y,z….)=>x+y+z+…+//无参数()=>1//返回对象x=>({obj:x})//注意符号,避免和函数体的{}冲突使用箭头函数时,函数体内的this对象,就是定义时所在的对象3.Promise定义:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作关于同步&异步JavaScript是基于事件驱动的单线程运行机制(why: 浏览器中至少有三个线程:js引擎线程,gui渲染线程,浏览器事件触发线程js操作dom,浏览器事件触发都会影响gui渲染效果,因此他们之间存在互斥的关系,使用多线程会带来非常复杂的同步问题(a线程在某个DOM节点添加内容。b线程删除了该节点)同步:即单线程模式,所有的任务都在主线程上执行,形成一个执行栈*,如函数调用后需要等待函数执行结束后才能进行下一个任务,如果某个任务执行时间过长(如死循环),容易造成线程阻塞,影响下面任务的正常进行异步:可以一起执行多个任务,函数调用后不会立刻就返回执行结果,异步任务会在当前脚本所有的同步任务执行结束后再执行。异步任务不进入主线程,而是进入任务队列,在某个任务可以执行时,等待主线程读取任务队列,随后该任务将进入主线程执行异步任务的实现,最经典的就是setTimeout()/setInterval()他们的内部运行机制完全一样,前者指定的代码只执行一次,后者为反复执行setTimeout(function(){ console.log(“taskA,yibu”);},0)console.log(“taskB,tongbu”);//taskB,tongbu//taskA,yibu即使延时事件为0,但由于它属于异步任务,仍需要等待同步任务执行结束后再执行综合看,整体的执行顺序为:先执行执行栈中的内容,执行完毕后,读取任务队列,寻找对应的异步任务,结束等待状态,进入执行栈执行,不断循环(Event Loop)只要主线程空了,就会去读取任务队列关于回调函数,callback()回调函数即是会被主线程挂起的代码异步任务必须指定回调函数,当主线程开始读取任务队列,执行异步任务的时候,执行的就是对应的回调函数言归正传,继续理解Promisepromise的三种状态:pending:初始状态fulfilled:操作成功rejected:操作失败Promise可以由1->2/1->3一旦状态变化,便会一直保持这个状态,不再改变。当状态改变Promise.then绑定的函数就会被调用构建Promisevar promise = new Promise(function(resolve,reject){ if(/操作成功/) resolve(data); else reject(error);});异步操作成功调用resolve,将结果作为参数传递出去异步操作失败调用reject,将报出的错误作为参数传递出去Promise构建完成后,使用then方法指定resolve状态和reject状态的回调函数promise.then(成功回调函数,失败的回调函数(非必要))//这两个函数都接受promise传出的值作为参数promise.then(function(data){do xxxx for success},function(error){do xxxx for failure});Promise新建后就执行,then方法指定的回调函数会在当前脚本的所有同步任务执行结束后再执行例子:var promise = new Promise(function(resolve, reject) { console.log(‘before resolved’); resolve(); console.log(‘after resolved’);});promise.then(function() { console.log(‘resolved’);});console.log(‘outer’);//before resolved//after resolved//outer//resolvedPromise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。能够简化层层回调的写法。Promise的精髓在于,用维护状态、传递状态的方式使得回调函数能够及时调用,比传递callback要简单、灵活Promise的其他方法.catch()用于指定发生错误时的回调函数,等同于reject部分和reject的区别:promise.then(onFulfilled,onRejected)在onFulfilled发生异常,在onRejected中捕获不到promise.then(onFulfilled).catch(onRejected)能够捕获异常。也可以用then替换,只是写法不同。本质上没有区别.all()用于将多个Promise实例包装成一个新的Promise实例var p = Promise.all([p1, p2, p3]);p1p2p3都需为promise实例当p1p2p3都为fulfilled时,p才会变为fulfilled只要有一个变为rejected,p就会变成rejected.race()用于将多个Promise实例包装成一个新的Promise实例与all()的区别类似于 AND 和 ORp1p2p3有一个状态发生改变,p的状态就发生改变,并返回第一个改变状态的promsie返回值,传递给p.resolve()看作new Promise()的快捷方式实例:Promise.resolve(‘Success’);/等同于/new Promise(function (resolve) { resolve(‘Success’);});让对象立刻进入resolved状态4.generate可以将generate理解为一个能够多次返回的“函数”function* foo(x){ yield x++; yield x+2; yield x+3; return xx;}由function定义,并且yield也可以返回数据调用generate的方法:不断的使用next()var f = foo(0);console.log(f.next());// 0 falseconsole.log(f.next());// 1 falseconsole.log(f.next());// 3 falseconsole.log(f.next());// 4 true使用for of结构for (var x of foo(0)) { console.log(x); // 依次输出0 1 3 (没有输出4原因不详)}每执行一次后就暂停,返回的值就是yield的返回值,每次返回一个值,直到done为true,这个generate对象已经全部执行完毕generate更像一个能够记住执行状态的函数实际上generate不算是一个函数,它的返回值不是变量也不是函数,而是一个可迭代的对象该对象类似一个元素被定义好的数组,保存的是一种规则而不元素本身,不能够随机访问,遍历也只能够遍历一次,因为规则只保存了上次的状态参考文档1:讲解JavaScript的线程运作参考文档2:讲解Promise参考文档3:关于generate ...

April 16, 2019 · 1 min · jiezi

JavaScript异步流程控制

JavaScript特性JavaScript属于单线程语言,即在同一时间,只能执行一个任务。在执行任务时,所有任务需要排队,前一个任务结束,才会执行后一个任务。当我们向后台发送一个请求时,主线程读取 “向后台发送请求” 这个事件并执行之后,到获取后台返回的数据这一过程会有段时间间隔,这时CPU处于空闲阶段,直到获取数据后再继续执行后面的任务,这就降低了用户体验度,使得页面加载变慢。于是,所有任务可以分成两种:同步任务和异步任务。同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制,这个过程会不断重复。“任务队列"是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。JavaScript异步实现的5种方式1. callback(回调函数)回调函数,也被称为高阶函数,是一个被作为参数传递给另一个函数并在该函数中被调用的函数。看一个在JQuery中简单普遍的例子:// 注意: click方法是一个函数而不是变量$("#button”).click(function() { alert(“Button Clicked”);}); 可以看到,上述例子将一个函数作为参数传递给了click方法,click方法会调用该函数,这是JavaScript中回调函数的典型用法,它在jQuery中广泛被使用。它不会立即执行,因为我们没有在后面加( ),而是在点击事件发生时才会执行。比如,我们要下载一个gif,但是不希望在下载的时候阻断其他程序,可以实现如下:downloadPhoto(‘http://coolcats.com/cat.gif', handlePhoto)function handlePhoto (error, photo) { if (error) { console.error(‘Download error!’, error); } else { console.log(‘Download finished’, photo); }}console.log(‘Download started’)首先声明handlePhoto函数,然后调用downloadPhoto函数并传递handlePhoto作为其回调函数,最后打印出“Download started”。请注意,handlePhoto尚未被调用,它只是被创建并作为回调传入downloadPhoto。但直到downloadPhoto完成其任务后才能运行,这可能需要很长时间,具体取决于Internet连接的速度,所以运行代码后,会先打印出Download started。这个例子是为了说明两个重要的概念:handlePhoto回调只是稍后存储一些事情的一种方式;事情发生的顺序不是从顶部到底部读取,而是基于事情完成时跳转;1. callback hell(回调地狱)var fs = require(‘fs’);/** * 如果三个异步api操作的话 无法保证他们的执行顺序 * 我们在每个操作后用回调函数就可以保证执行顺序 */ fs.readFile(’./data1.json’, ‘utf8’, function(err, data){ if (err) { throw err; } else { console.log(data); fs.readFile(’./data2.json’, ‘utf8’, function(err, data){ if (err) { throw err; } else { console.log(data) fs.readFile(’./data3.json’, ‘utf8’, function(err, data){ if (err) { throw err; } else { console.log(data); } }) } }) }})有没有看到这些以"})“结尾的金字塔结构?由于回调函数是异步的,在上面的代码中每一层的回调函数都需要依赖上一层的回调执行完,所以形成了层层嵌套的关系最终形成类似上面的回调地狱。2. 代码层面解决回调地狱1. 保持代码简短var form = document.querySelector(‘form’)form.onsubmit = function formSubmit (submitEvent) { var name = document.querySelector(‘input’).value request({ uri: “http://example.com/upload", body: name, method: “POST” }, function postResponse (err, response, body) { var statusMessage = document.querySelector(’.status’) if (err) return statusMessage.value = err statusMessage.value = body })}可以看到,上面的代码给两个函数加了描述性功能名称,使代码更容易阅读,当发生异常时,你将获得引用实际函数名称而不是“匿名”的堆栈跟踪。现在我们可以将这些功能移到我们程序的顶层:document.querySelector(‘form’).onsubmit = formSubmit;function formSubmit (submitEvent) { var name = document.querySelector(‘input’).value; request({ uri: “http://example.com/upload", body: name, method: “POST” }, postResponse);} function postResponse (err, response, body) { var statusMessage = document.querySelector(’.status’); if (err) return statusMessage.value = err; statusMessage.value = body;}重新整改代码结构之后,可以清晰的看到这段函数的功能。2. 模块化从上面取出样板代码,并将其分成几个文件,将其转换为模块。这是一个名为formuploader.js的新文件,它包含了之前的两个函数:module.exports.submit = formSubmit;function formSubmit (submitEvent) { var name = document.querySelector(‘input’).value; request({ uri: “http://example.com/upload", body: name, method: “POST” }, postResponse)}function postResponse (err, response, body) { var statusMessage = document.querySelector(’.status’); if (err) return statusMessage.value = err; statusMessage.value = body;}把它们exports后,在应用程序中引入并使用,这就使得代码更加简洁易懂了:var formUploader = require(‘formuploader’);document.querySelector(‘form’).onsubmit = formUploader.submit;3. error first处理每一处错误,并且回调的第一个参数始终保留用于错误:var fs = require(‘fs’) fs.readFile(’/Does/not/exist’, handleFile); function handleFile (error, file) { if (error) return console.error(‘Uhoh, there was an error’, error); // otherwise, continue on and use file in your code; }有第一个参数是错误是一个简单的惯例,鼓励你记住处理你的错误。如果它是第二个参数,会更容易忽略错误。除了上述代码层面的解决方法,还可以使用以下更高级的方法,也是另外4种实现异步的方法。但是请记住,回调是JavaScript的基本组成部分(因为它们只是函数),在学习更先进的语言特性之前学习如何读写它们,因为它们都依赖于对回调。2. 发布订阅模式订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。比如有个界面是实时显示天气,它就订阅天气事件(注册到调度中心,包括处理程序),当天气变化时(定时获取数据),就作为发布者发布天气信息到调度中心,调度中心就调度订阅者的天气处理程序。简单来说,发布订阅模式,有一个事件池,用来给你订阅(注册)事件,当你订阅的事件发生时就会通知你,然后你就可以去处理此事件。使用发布订阅模式,来修改Ajax:xhr.onreadystatechange = function () {//监听事件 if (this.readyState === 4) { if (this.status === 200) { switch (dataType) { case ‘json’: { Event.emit(‘data ‘+method,JSON.parse(this.responseText)); //触发事件 break; } case ’text’: { Event.emit(‘data ‘+method,this.responseText); break; } case ‘xml’: { Event.emit(‘data ‘+method,this.responseXML); break; } default: { break; } } } }}3. PromiseES6将Promise写进了语言标准,统一了用法,原生提供了Promise对象。Promise,简单说就是一个容器,里面保存着一个异步操作的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise有3种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。Promise很重要的两个特点:状态不受外界影响;只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。一旦状态改变,就不会再变,任何时候都可以得到这个结果;Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型)。1. 基本用法const p = new Promise((resolve,reject) => { // resolve在异步操作成功时调用 resolve(‘success’); // reject在异步操作失败时调用 reject(’error’);});p.then(result => { console.log(result);});p.catch(result => { console.log(result);})ES6规定,Promise对象是一个构造函数,用来生成Promise实例。new一个Promise实例时,这个对象的起始状态就是Pending状态,再根据resolve或reject返回Fulfilled状态 / Rejected状态。2. Promise.prototype.then( )前面可以看到,Promise实例具有then方法,所以then方法是定义在原型对象Promise.prototype上的,它的作用是为Promise实例添加状态改变时的回调函数。then方法返回的是一个新的Promise实例,因此then可以采用链式写法:getJSON("/posts.json”).then(function(json) { return json.post;}).then(function(post) { // …});3. Promise.prototype.catch( )Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。getJSON(’/posts.json’).then(function(posts) { // …}).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log(‘发生错误!’, error);});4. Promise.all( )Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。const p = Promise.all([p1, p2, p3]);上面代码中,p的状态由p1、p2、p3决定,分成两种情况:只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。5. Promise.race( )Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。不同的是,race()接受的对象中,哪个对象返回快就返回哪个对象,如果指定时间内没有获得结果,就将Promise的状态变为reject。const p = Promise.race([ fetch(’/resource-that-may-take-a-while’), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error(‘request timeout’)), 5000) })]);p.then(console.log).catch(console.error);上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。6. Promise.resolve( )Promise.resolve(‘foo’)// 等价于new Promise(resolve => resolve(‘foo’))7. Promise.reject( )const p = Promise.reject(‘出错了’);// 等同于const p = new Promise((resolve, reject) => reject(‘出错了’))p.then(null, function (s) { console.log(s)});// 出错了下面是一个用Promise对象实现的Ajax操作的例子:const getJSON = function(url) { const promise = new Promise(function(resolve, reject){ const handler = function() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; const client = new XMLHttpRequest(); client.open(“GET”, url); client.onreadystatechange = handler; client.responseType = “json”; client.setRequestHeader(“Accept”, “application/json”); client.send(); }); return promise;};getJSON("/posts.json”).then(function(json) { console.log(‘Contents: ’ + json);}, function(error) { console.error(‘出错了’, error);});8. callbackify & promisifyNode 8提供了两个工具函数util.promisify、util.callbackify用于在回调函数和Promise之间做方便的切换,我们也可以用JavaScript代码来实现一下。1. promisify:把callback转化为promisefunction promisify(fn_callback) { //接收一个有回调函数的函数,回调函数一般在最后一个参数 if(typeof fn_callback !== ‘function’) throw new Error(‘The argument must be of type Function.’); //返回一个函数 return function (…args) { //返回Promise对象 return new Promise((resolve, reject) => { try { if(args.length > fn_callback.length) reject(new Error(‘arguments too much.’)); fn_callback.call(this,…args,function (…args) { //nodejs的回调,第一个参数为err, Error对象 args[0] && args[0] instanceof Error && reject(args[0]); //除去undefined,null参数 args = args.filter(v => v !== undefined && v !== null); resolve(args); }.bind(this)); //保证this还是原来的this } catch (e) { reject(e) } }) }}2. callbackify:promise转换为callbackfunction callbackify(fn_promise) { if(typeof fn_promise !== ‘function’) throw new Error(‘The argument must be of type Function.’); return function (…args) { //返回一个函数 最后一个参数是回调 let callback = args.pop(); if(typeof callback !== ‘function’) throw new Error(‘The last argument must be of type Function.’); if(fn_promise() instanceof Promise){ fn_promise(args).then(data => { //回调执行 callback(null,data) }).catch(err => { //回调执行 callback(err,null) }) }else{ throw new Error(‘function must be return a Promise object’); } }}个人而言,最好直接把代码改成promise形式的,而不是对已有的callback加上这个中间层,因为其实改动的成本差不多。但总有各种各样的情况,比如,你的回调函数已经有很多地方使用了,牵一发而动全身,这时这个中间层还是比较有用的。4. generator(生成器)函数Generator函数是ES6提供的一种异步编程解决方案,通过yield标识位和next()方法调用,实现函数的分段执行。1. next( )方法先从下面的例子看一下Generator函数是怎么定义和运行的。function gen() { yield “hello”; yield “generator”; return;}gen(); // 没有输出结果var g = gen();console.log(g.next()); // { value: ‘hello’, done: false }console.log(g.next()); // { value: ‘generator’, done: false }console.log(g.next()); // { value: ‘undefined’, done: true }从上面可以看到,Generator函数定义时要带,在直接执行gen()时,没有像普通的函数一样,输出结果,而是通过调用next()方法得到了结果。这个例子中我们引入了yield关键字,分析下这个执行过程:创建了g对象,指向gen的句柄第一次调用next(),执行到yield hello,暂缓执行,并返回了hello第二次调用next(),继续上一次的执行,执行到yield generator,暂缓执行,并返回了generator第三次调用next(),直接执行return,并返回done:true,表明结束。经过上面的分析,yield实际就是暂缓执行的标示,每执行一次next(),相当于指针移动到下一个yield位置。next()方法返回的结果是个对象,对象里面的value是运行结果,done表示是否运行完成。2. throw( )方法throw()方法在函数体外抛出一个错误,然后在函数体内捕获。function *gen1() { try{ yield; } catch(e) { console.log(‘内部捕获’) }}let g1 = gen1();g1.next();g1.throw(new Error());3. return( )方法return()方法返回给定值,并终结生成器,在return后面的yield不会再被执行。function *gen2(){ yield 1; yield 2; yield 3;}let g2 = gen2();g2.next(); // { value:1, done:false }g2.return(); // { value:undefined, done:true }g2.next(); // { value:undefined, done:true }5. Promise + async & await在ES2017中,提供了async / await两个关键字来实现异步,是异步编程的最高境界,就是根本不用关心它是否是异步,很多人认为它是异步编程的终极解决方案。async / await寄生于Promise,本质上还是基于Generator函数,可以说是Generator函数的语法糖,async用于申明一个function是异步的,而await可以认为是async wait的简写,等待一个异步方法执行完成。async function demo() { let result = await Promise.resolve(123); console.log(result);}demo();async函数返回的是一个Promise对象,在上述例子中,表示demo是一个async函数,await只能用在async函数里面,表示等待Promise返回结果后,再继续执行,await后面应该跟着Promise对象(当然,跟着其他返回值也没关系,只是会立即执行,这样就没有意义了)。Promise虽然一方面解决了callback的回调地狱,但是相对的把回调 “纵向发展” 了,形成了一个回调链:function sleep(wait) { return new Promise((res,rej) => { setTimeout(() => { res(wait); },wait); });}/let p1 = sleep(100);let p2 = sleep(200);let p =/sleep(100).then(result => { return sleep(result + 100);}).then(result02 => { return sleep(result02 + 100);}).then(result03 => { console.log(result03);})将上述代码改成async/await写法:async function demo() { let result01 = await sleep(100); //上一个await执行之后才会执行下一句 let result02 = await sleep(result01 + 100); let result03 = await sleep(result02 + 100); // console.log(result03); return result03;}demo().then(result => { console.log(result);});因为async返回的也是promise对象,所以用then接收就行了。如果是reject状态,可以用try-catch捕捉:let p = new Promise((resolve,reject) => { setTimeout(() => { reject(’error’); },1000);});async function demo(params) { try { let result = await p; } catch(e) { console.log(e); }}demo();这是基本的错误处理,但是当内部出现一些错误时,和Promise有点类似,demo()函数不会报错,还是需要catch回调捕捉,这就是内部的错误被 “静默” 处理了。let p = new Promise((resolve,reject) => { setTimeout(() => { reject(’error’); },1000);});async function demo(params) { // try { let result = name; // } catch(e) { // console.log(e); // }}demo().catch((err) => { console.log(err);})最后,总结一下JavaScript实现异步的5种方式的优缺点:回调函数:写起来方便,但是过多的回调会产生回调地狱,代码横向扩展,不易于维护和理解。发布订阅模式:方便管理和修改事件,不同的事件对应不同的回调,但是容易产生一些命名冲突的问题,事件到处触发,可能代码可读性不好。Promise对象:通过then方法来替代掉回调,解决了回调产生的参数不容易确定的问题,但是相对的把回调 “纵向发展” 了,形成了一个回调链。Generator函数:确实很好的解决了JavaScript中异步的问题,但是得依赖执行器函数。async/await:这可能是javascript中,解决异步的最好的方式了,让异步代码写起来跟同步代码一样,可读性和维护性都上来了。 ...

April 9, 2019 · 4 min · jiezi

Generator函数与async函数对比

Generator函数与async函数对比Generator函数:Generator函数是ES2015提供的异步解决方案,与普通函数有很大的不同;特征:在function关键字后面跟一个()号;在函数体内部使用yield表达式作为一个状态;Generator函数返回一个遍历器,可通过for……of方法遍历每个状态;用法:执行Generator并不立刻执行,返回一个遍历器,遍历器通过调用next()、throw()或者return()执行下一个状态、捕获错误或者结束遍历器;async函数:async函数是ES2017提供的异步函数语法,是generator的语法糖,但是用法上与Generator函数还是有很大不同;特征:在function关键字前面跟一个async关键字;在函数体内部使用await表达式;async函数返回一个promise对象;用法:执行async函数会立刻执行,和普通函数一样,但是返回一个promise对象;两者对比:Generator 出现在ES2015中,async 出现在ES2017中,async 是 Generator 的语法糖;执行方式不同,Generator 执行需要使用执行器(next()等方法);async 函数自带执行器,与普通函数的执行一样;async 的语法语义更加清楚,async 表示异步,await 表示等待;而 Generator 函数的()号和 yield 的语义就没那么直接了;Generator 中 yield 后面只能跟 Thunk 函数或 Promise 对象;而 async 函数中 await 后面可以是 promise 对象或者原始类型的值(会自动转为立即resovle的promise对象);返回值不同,Generator 返回遍历器,相比于 async 返回 promise 对象操作更加麻烦。参考:ECMAScript 6入门

April 3, 2019 · 1 min · jiezi

Ajax Fetch 和 Axios(持续更新中...)

Ajax Fetch 和 Axios(持续更新中…)知识点梳理AJAX 不是 JavaScript 的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用 JavaScript 执行异步网络请求。JavaScript 代码都是单线程执行的,由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,回调函数不好看,不利于代码复用,而链式写法好处 逻辑统一、利于复用,所以出现 Primose Promise 有各种开源实现,在 ES6 中被统一规范,由浏览器直接支持。async await 是 Promise 语法糖,使异步的逻辑书写标准的同步函数Generator原生 XHRAjaxAJAX 不是 JavaScript 的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用 JavaScript 执行异步网络请求。属性描述onreadystatechange每当 readyState 属性改变时,就会调用该函数。readyState存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。0: 请求未初始化 1: 服务器连接已建立,open()方法已调用,但是 send()方法未调用 2: 请求已连接 send()方法已调用,HTTP 请求已发送到 Web 服务器。未接收到相应3: 请求处理中 4: 请求已完成,且响应已就绪status200: “OK"404: 未找到页面;(function() { var xmlhttp if (window.XMLHttpRequest) { // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码 xmlhttp = new XMLHttpRequest() } else { // IE6, IE5 浏览器执行代码 xmlhttp = new ActiveXObject(‘Microsoft.XMLHTTP’) } xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { console.log(xmlhttp.responseText) } } xmlhttp.open(‘GET’, ‘http://www.runoob.com/try/ajax/ajax_info.txt', true) xmlhttp.send()})()Ajax 安全限制浏览器的同源策略导致的。默认情况下,JavaScript 在发送 AJAX 请求时,URL 的域名必须和当前页面完全一致跨域请求通过 Flash 插件发送 HTTP 请求,这种方式可以绕过浏览器的安全限制,但必须安装 Flash,并且跟 Flash 交互。不过 Flash 用起来麻烦,而且现在用得也越来越少了。通过在同源域名下架设一个代理服务器来转发,JavaScript 负责把请求发送到代理服务器JSONP 它有个限制,只能用 GET 请求,并且要求返回 JavaScript。这种方式跨域实际上是利用了浏览器允许跨域引用 JavaScript 资源CORS CORS 全称 Cross-Origin Resource Sharing,是 HTML5 规范定义的如何跨域访问资源。面这种跨域请求,称之为“简单请求”。简单请求包括 GET、HEAD 和 POST(POST 的 Content-Type 类型仅限 application/x-www-form-urlencoded、multipart/form-data 和 text/plain),并且不能出现任何自定义头(例如,X-Custom: 12345),通常能满足 90%的需求Promise在 JavaScript 的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现function callback() { console.log(‘Done’)}console.log(‘before setTimeout()’)setTimeout(callback, 1000) // 1秒钟后调用callback函数console.log(‘after setTimeout()’)链式写法的好处在于,先统一执行 AJAX 逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用 success 函数或 fail 函数。古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在 JavaScript 中称为 Promise 对象。使用 Promise 封装 ajax 简化异步处理// ajax函数将返回Promise对象:function ajax(method, url, data) { var request = new XMLHttpRequest() return new Promise(function(resolve, reject) { request.onreadystatechange = function() { if (request.readyState === 4) { if (request.status === 200) { resolve(request.responseText) } else { reject(request.status) } } } request.open(method, url) request.send(data) })}var p = ajax(‘GET’, ‘/api/categories’)p.then(function(text) { // 如果AJAX成功,获得响应内容}).catch(function(status) { // 如果AJAX失败,获得响应代码})Promise 使用方法;(function() { console.time(‘doIt’) const time1 = 300 step1(time1) .then(time2 => step2(time2)) .then(time3 => step3(time3)) .then(result => { console.log(result is ${result}) console.timeEnd(‘doIt’) })})()function takeLongTime(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n) })}function step1(n) { console.log(step1 with ${n}) return takeLongTime(n)}function step2(n) { console.log(step2 with ${n}) return takeLongTime(n)}function step3(n) { console.log(step3 with ${n}) return takeLongTime(n)}Promise.all()两个任务是可以并行执行的,用 Promise.all()实现var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, ‘P1’)})var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 6000, ‘P2’)})// 同时执行p1和p2,并在它们都完成后执行then:Promise.all([p1, p2]).then(function(results) { console.log(results) // 获得一个Array: [‘P1’, ‘P2’]})Promise.race()有些时候,多个异步任务是为了容错,只需要获得先返回的结果即可var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 3000, ‘P1’)})var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 6000, ‘P2’)})// 同时执行p1和p2,其中一个完成后执行then:Promise.race([p1, p2]).then(function(results) { console.log(results) // // ‘P1’})async awaitawaitawait 操作符用于等待一个 Promise 对象。它只能在异步函数 async function 中使用await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的 resolve 函数参数作为 await 表达式的值,继续执行 async function。若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。// 如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x) }, 2000) })}async function f1() { var x = await resolveAfter2Seconds(10) console.log(x) // 10}f1()// 如果 Promise 处理异常,则异常值被抛出。async function f3() { try { var z = await Promise.reject(30) } catch (e) { console.log(e) // 30 }}f3()asyncasync function 声明用于定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。一个返回的 Promise 对象会以 async function 的返回值进行解析(resolved),或者以该函数抛出的异常进行回绝(rejected)。async 函数中可能会有 await 表达式,这会使 async 函数暂停执行,等待 Promise 的结果出来,然后恢复 async 函数的执行并返回解析值(resolved)。function resolveAfter2Seconds() { return new Promise(resolve => { setTimeout(() => { resolve(‘resolved’) }, 2000) })}async function asyncCall() { console.log(‘calling’) var result = await resolveAfter2Seconds() console.log(result) // expected output: ‘resolved’}asyncCall()GeneratorFetchFetch 是一个现代的概念, 等同于 XMLHttpRequest。它提供了许多与 XMLHttpRequest 相同的功能,但被设计成更具可扩展性和高效性。Fetch 是挂在在 window 下的Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,以及用于初始化异步请求的 global fetch。Fetch 还利用到了请求的异步特性——它是基于 Promise 的。fetch(‘http://example.com/movies.json') .then(function(response) { return response.json() }) .then(function(myJson) { console.log(myJson) })postData(‘http://example.com/answer', { answer: 42 }) .then(data => console.log(data)) // JSON from response.json() call .catch(error => console.error(error))function postData(url, data) { // Default options are marked with * return fetch(url, { body: JSON.stringify(data), // must match ‘Content-Type’ header cache: ’no-cache’, // *default, no-cache, reload, force-cache, only-if-cached credentials: ‘same-origin’, // include, same-origin, *omit headers: { ‘user-agent’: ‘Mozilla/4.0 MDN Example’, ‘content-type’: ‘application/json’ }, method: ‘POST’, // *GET, POST, PUT, DELETE, etc. mode: ‘cors’, // no-cors, cors, *same-origin redirect: ‘follow’, // manual, *follow, error referrer: ’no-referrer’ // *client, no-referrer }).then(response => response.json()) // parses response to JSON}参考AJAXPromiseasync await 语法描述Fetch 语法描述 ...

February 21, 2019 · 3 min · jiezi

Python高级语法之:一篇文章了解yield与Generator生成器

Python高级语法中,由一个yield关键词生成的generator生成器,是精髓中的精髓。它虽然比装饰器、魔法方法更难懂,但是它强大到我们难以想象的地步:小到简单的for loop循环,大到代替多线程做服务器的高并发处理,都可以基于yield来实现。理解yield:代替return的yield简单来说,yield是代替return的另一种方案:return就像人只有一辈子,一个函数一旦return,它的生命就结束了yield就像有“第二人生”、“第三人生”甚至轮回转世一样,函数不但能返回值,“重生”以后还能再接着“上辈子”的记忆继续返回值我的定义:yield在循环中代替return,每次循环返回一次值,而不是全部循环完了才返回值。yield怎么念?return我们念“返回xx值”,我建议:yield可以更形象的念为"呕吐出xx值“,每次呕一点。一般我们进行循环迭代的时候,都必须等待循环结束后才return结果。数量小的时候还行,但是如果循环次数上百万?上亿?我们要等多久?如果循环中不涉及I/O还行,但是如果涉及I/O堵塞,一个堵几秒,后边几百万个客户等着呢,银行柜台还能不能下班了?所以这里肯定是要并行处理的。除了传统的多线程多进程外,我们还可以选择Generator生成器,也就是由yield代替return,每次循环都返回值,而不是全部循环完了才返回结果。这样做的好处就是——极大的节省了内存。如果用return,那么循环中的所有数据都要不断累计到内存里直到循环结束,这个不友好。而yield则是一次一次的返回结果,就不会在内存里累加了。所以数据量越大,优势就越明显。有多明显?如果做一百万的简单数字计算,普通的for loop return会增加300MB+的内存占用!而用yield一次一次返回,增加的内存占用几乎为0MB!yield的位置既然yield不是全部循环完了再返回,而是循环中每次都返回,所以位置自然不是在for loop之后,而是在loop之中。先来看一般的for loop返回:def square(numbers): result = [] for n in numbers: result.append( n2 ) return result #在for之外再来看看yield怎么做:def square(numbers): for n in numbers: yield n2 #在for之中可以看到,yield在for loop之中,且函数完全不需要写return返回。这时候如果你print( square([1,2,3]) )得到的就不是直接的结果,而是一个<generator object>。如果要使用,就必须一次一次的next(…)来获取下一个值:>>> results = square( [1,2,3] )>>> next( result )1>>> next( result )4>>> next( result )9>>> next( result )ERROR: StopIteration这个时候更简单的做法是:for r in results: print( r )因为in这个关键词自动在后台为我们调用生成器的next(..)函数什么是generator生成器?只要我们在一个函数中用了yield关键字,函数就会返回一个<generator object>生成器对象,两者是相辅相成的。有了这个对象后,我们就可以使用一系列的操作来控制这个循环结果了,比如next(..)获取下一个迭代的结果。yield和generator的关系,简单来说就是一个起因一个结果:只要写上yield, 其所在的函数就立马变成一个<generator object>对象。xrange:用生成器实现的rangePython中我们使用range()函数生成数列非常常用。而xrange()的使用方法、效果几乎一模一样,唯一不同的就是——xrange()返回的是生成器,而不是直接的结果。如果数据量大时,xrange()能极大的减小内存占用,带来卓越的性能提升。当然,几百、几千的数量级,就直接用range好了。多重yield有时候我们可能会在一个函数中、或者一个for loop中看到多个yield,这有点不太好理解。但其实很简单!一般情况下,我们写的:for n in [1,2,3]: yield n2实际上它的本质是生成了这个东西:yield 12yield 22yield 32也就是说,不用for loop,我们自己手写一个一个的yield,效果也是一样的。你每次调用一次next(..),就得到一个yield后面的值。然后三个yield的第一个就会被划掉,剩两个。再调用一次,再划掉一个,就剩一个。直到一个都不剩,next(..)就返回异常。一旦了解这个本质,我们就能理解一个函数里写多个yield是什么意思了。更深入理解yield:作为暂停符的yield从多重yield延伸,我们可以开始更进一步了解yield到底做了些什么了。现在,我们不把yield看作是return的替代品了,而是把它看作是一个suspense暂停符。即每次程序遇到yield,都会暂停。当你调用next(..)时候,它再resume继续。比如我们改一下上面的程序:def func(): yield 12 print(‘Hi, Im A!’) yield 22 print(‘Hi, Im B!’) yield 32 print(‘Hi, Im C!’)然后我们调用这个小函数,来看看yield产生的实际效果是什么:>>> f = func()>>> f<generator object func at 0x10d36c840>>>> next( f )1>>> next( f )Hi, Im A!4>>> next( f )Hi, Im B!9>>> next( f )Hi, Im C!ERROR: StopIteration从这里我们可以看到:第一次调用生成器的时候,yield之后的打印没有执行。因为程序yield这里暂停了第二次调用生成器的时候,第一个yield之后的语句执行了,并且再次暂停在第二个yield第三次调用生成器的时候,卡在了第三个yield。第四次调用生成器的时候,最后一个yield以下的内容还是执行了,但是因为没有找到第四个yield,所以报错。所以到了这里,如果我们能理解yield作为暂停符的作用,就可以非常灵活的用起来了。yield from与sub-generator子生成器yield from是Python 3.3开始引入的新特性。它主要作用就是:当我需要在一个生成器函数中使用另一个生成器时,可以用yield from来简化语句。举例,正常情况下我们可能有这么两个生成器,第二个调用第一个:def gen1(): yield 11 yield 22 yield 33def gen2(): for g in gen1(): yield g yield 44 yield 55 yield 66可以看到,我们在gen2()这个生成器中调用了gen1()的结果,并把每次获取到的结果yield转发出去,当成自己的yield出来的值。我们把这种一个生成器中调用的另一个生成器叫做sub-generator子生成器,而这个子生成器由yield from关键字生成。由于sub-generator子生成器很常用,所以Python引入了新的语法来简化这个代码:yield from。上面gen2()的代码可以简化为:def gen2(): yield from gen1() yield 44 yield 55 yield 66这样看起来是不是更"pythonic"了呢?:)所以只要记住:yield from只是把别人呕吐出来的值,直接当成自己的值呕吐出去。递归+yield能产生什么?一般我们只是二选一:要不然递归,要不然for循环中yield。有时候yield就可以解决递归的问题,但是有时候光用yield并不能解决,还是要用递归。那么怎么既用到递归,又用到yield生成器呢?参考:Recursion using yielddef func(n): result = n2 yield result if n < 100: yield from func( result )for x in func(100): print( x )上面代码的逻辑是:如果n小于100,那么每次调用next(..)的时候,都得到n的乘方。下次next,会继续对之前的结果进行乘方,直到结果超过100为止。我们看到代码里利用了yield from子生成器。因为yield出的值不是直接由变量来,而是由“另一个”函数得来了。 ...

February 19, 2019 · 1 min · jiezi

深入理解ES6之迭代器与生成器

迭代器迭代器 iterator,在 Javascript 中,迭代器是一个对象(也可称作为迭代器对象),它提供了一个 next() 方法,用来返回迭代序列中的下一项。next 方法的定义,next 方法是一个函数,执行后返回一个对象包含两个属性:{ done: [boolean], value: [any] }// 创建一个迭代器对象function makeIterator(array) { var nextIndex = 0 return { next() { return nextIndex < array.length ? { value: array[nextIndex++], done: false } : { done: true } } }}// iterator 是一个迭代器对象var iterator = makeIterator([10, 20, 30])iterator.next() // {value: 10, done: false}iterator.next() // {value: 20, done: false}iterator.next() // {value: 30, done: false}iterator.next() // {done: true}可迭代对象可迭代对象必须实现一个 @@iterator 方法,也就是说在这个对象或者它的原型链上必须有一个方法名是 Symbol.iterator 的方法,当调用这个方法时它返回一个迭代器对象。可迭代对象的表现形式为,可以使用 for…of 循环,解构赋值,拓展运算符(spread),yield* 这些语法来调用 Symbol.iterator 函数。也就是说这些语法糖在被调用时本质上都是在调用 Symbol.iterator 函数。内置可迭代对象String,Array,TypedArray,Map,Set,函数的arguments对象,NodeList对象都是内置的可迭代对象,他们的原型对象中都有一个 Symbol.iterator 方法。// 可迭代对象let iterable = [10, 20, 30]// 继承自原型链Symbol.iterator in iterable // trueiterable.hasOwnProperty(Symbol.iterator) // falsefor(let value of iterable){ console.log(value)}// 10// 20// 30自定义可迭代对象字面量对象 let o = {} 默认没有 Symbol.iterator 方法,但是我们在对象上自定义一个 @@iterator 方法,此时字面量对象也可以使用 for…of循环,拓展运算符等等语法糖。// 字面量对象默认是不可迭代对象// 自定义对var myIterable = {}myIterable[Symbol.iterator] = function(){ return { arr: [10, 20, 30], next: function(){ if(this.arr.length > 0){ return {value: this.arr.shift(), done: false} }else{ return {done: true} } } }}[…myIterable] // [10, 20, 30]生成器生成器 generator,在 Javascript 中生成器是一个函数(也可称作生成器函数),它可以作为创建迭代器的工厂函数。生成器函数的返回值是一个迭代器对象,同时这个对象也是一个可迭代对象。funtion* 这种声明方式可以定义一个生成器函数。生成器函数的语法规则是,调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 (iterator )对象。当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。调用 next()方法时,如果传入了参数,那么这个参数会作为上一条执行的 yield 语句的返回值。// 生成器函数function* generator(i){ yield i + 1 var y = yield ‘foo’ yield y}var iterator = generator(10) // 此时生成器函数不执行,返回一个迭代器iterator.next() // {value: 11, done: false} iterator.next() // {value ‘foo’, done: false}iterator.next(10) // {value: 10, done: false},将10赋值给上一条 yield ‘foo’ 左侧的值,即 y = 10,返回 yiterator.next() // {done: true}既然生成器函数可以创建迭代器对象,我们来试着将前面的例子用生成器函数的形式重写试试看。// 生成器函数function* makeIterator(array) { for (let i = 0; i < array.length; i++) { yield array[i] }}// 迭代器对象,实现和上文一样的功能var iteratorByGenerator = makeIterator([10, 20, 30])iteratorByGenerator.next() // {value: 10, done: false}iteratorByGenerator.next() // {value: 20, done: false}iteratorByGenerator.next() // {value: 30, done: false}iteratorByGenerator.next() // {done: true}从上面的代码我们可以看到,利用生成器函数来创建一个迭代器对象的方式相比于之前我们普通函数创建的方式更加简洁,也更加清晰的表明调用生成器函数返回的是一个迭代器对象。除此之外还有什么区别呢。上文已经提到,生成器函数返回的是一个可迭代的迭代器对象,这是什么意思呢?看下代码就明白了。// 生成器函数创建的迭代器对象Symbol.iterator in iteratorByGenerator // true[…iteratorByGenerator] // [10, 20, 30]// 普通函数创建的迭代器对象Symbol.iterator in iterator // false[…iterator] // Uncaught TypeError: iterator is not iterable综上所述,我们可以确定的说生成器函数是创建迭代器对象的语法糖,通过生成器函数我们可以用很简洁清晰的语法创建一个可迭代的迭代器对象。 ...

December 27, 2018 · 2 min · jiezi

ES6 系列之 Babel 将 Generator 编译成了什么样子

前言本文就是简单介绍下 Generator 语法编译后的代码。Generatorfunction* helloWorldGenerator() { yield ‘hello’; yield ‘world’; return ’ending’;}我们打印下执行的结果:var hw = helloWorldGenerator();console.log(hw.next()); // {value: “hello”, done: false}console.log(hw.next()); // {value: “world”, done: false}console.log(hw.next()); // {value: “ending”, done: true}console.log(hw.next()); // {value: undefined, done: true}Babel具体的执行过程就不说了,我们直接在 Babel 官网的 Try it out 粘贴上述代码,然后查看代码被编译成了什么样子:/** * 我们就称呼这个版本为简单编译版本吧 /var _marked = /#PURE/ regeneratorRuntime.mark(helloWorldGenerator);function helloWorldGenerator() { return regeneratorRuntime.wrap( function helloWorldGenerator$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return “hello”; case 2: _context.next = 4; return “world”; case 4: return _context.abrupt(“return”, “ending”); case 5: case “end”: return _context.stop(); } } }, _marked, this );}猛一看,好像编译后的代码还蛮少的,但是细细一看,编译后的代码肯定是不能用的呀,regeneratorRuntime 是个什么鬼?哪里有声明呀?mark 和 wrap 方法又都做了什么?难道就不能编译一个完整可用的代码吗?regenerator如果你想看到完整可用的代码,你可以使用 regenerator,这是 facebook 下的一个工具,用于编译 ES6 的 generator 函数。我们先安装一下 regenerator:npm install -g regenerator然后新建一个 generator.js 文件,里面的代码就是文章最一开始的代码,我们执行命令:regenerator –include-runtime generator.js > generator-es5.js我们就可以在 generator-es5.js 文件看到编译后的完整可用的代码。而这一编译就编译了 700 多行…… 编译后的代码可以查看 generator-es5.js总之编译后的代码还蛮复杂,我们可以从中抽离出大致的逻辑,至少让简单编译的那段代码能够跑起来。mark 函数简单编译后的代码第一段是这样的:var _marked = /#PURE/ regeneratorRuntime.mark(helloWorldGenerator);我们查看完整编译版本中 mark 函数的源码:runtime.mark = function(genFun) { genFun.proto = GeneratorFunctionPrototype; genFun.prototype = Object.create(Gp); return genFun;};这其中又涉及了 GeneratorFunctionPrototype 和 Gp 变量,我们也查看下对应的代码:function Generator() {}function GeneratorFunction() {}function GeneratorFunctionPrototype() {}…var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype);GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;GeneratorFunctionPrototype.constructor = GeneratorFunction;GeneratorFunctionPrototype[toStringTagSymbol] = GeneratorFunction.displayName = “GeneratorFunction”;这段代码构建了一堆看起来很复杂的关系链,其实这是参照着 ES6 规范构建的关系链:图中 +@@toStringTag:s = ‘Generator’ 的就是 Gp,+@@toStringTag:s = ‘GeneratorFunction’ 的就是 GeneratorFunctionPrototype。构建关系链的目的在于判断关系的时候能够跟原生的保持一致,就比如:function f() {}var g = f();console.log(g.proto === f.prototype); // trueconsole.log(g.proto.proto === f.proto.prototype); // true为了简化起见,我们可以把 Gp 先设置为一个空对象,不过正如你在上图中看到的,next()、 throw()、return() 函数都是挂载在 Gp 对象上,实际上,在完整的编译代码中,确实有为 Gp 添加这三个函数的方法:// 117 行function defineIteratorMethods(prototype) { [“next”, “throw”, “return”].forEach(function(method) { prototype[method] = function(arg) { return this._invoke(method, arg); }; });}// 406 行defineIteratorMethods(Gp);为了简单起见,我们将整个 mark 函数简化为:runtime.mark = function(genFun) { var generator = Object.create({ next: function(arg) { return this._invoke(’next’, arg) } }); genFun.prototype = generator; return genFun;};wrap 函数除了设置关系链之外,mark 函数的返回值 genFun 还作为了 wrap 函数的第二个参数传入:function helloWorldGenerator() { return regeneratorRuntime.wrap( function helloWorldGenerator$(_context) { … }, _marked, this );}我们再看下 wrap 函数:function wrap(innerFn, outerFn, self) { var generator = Object.create(outerFn.prototype); var context = new Context([]); generator._invoke = makeInvokeMethod(innerFn, self, context); return generator;}所以当执行 var hw = helloWorldGenerator(); 的时候,其实执行的是 wrap 函数,wrap 函数返回了 generator,generator 是一个对象,原型是 outerFn.prototype, outerFn.prototype 其实就是 genFun.prototype, genFun.prototype 是一个空对象,原型上有 next() 方法。所以当你执行 hw.next() 的时候,执行的其实是 hw 原型的原型上的 next 函数,next 函数执行的又是 hw 的 _invoke 函数:generator._invoke = makeInvokeMethod(innerFn, self, context);innerFn 就是 wrap 包裹的那个函数,其实就是 helloWordGenerato$ 函数,呐,就是这个函数:function helloWorldGenerator$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return “hello”; case 2: _context.next = 4; return “world”; case 4: return _context.abrupt(“return”, “ending”); case 5: case “end”: return _context.stop(); } }}而 context 你可以直接理解为这样一个全局对象:var ContinueSentinel = {};var context = { done: false, method: “next”, next: 0, prev: 0, abrupt: function(type, arg) { var record = {}; record.type = type; record.arg = arg; return this.complete(record); }, complete: function(record, afterLoc) { if (record.type === “return”) { this.rval = this.arg = record.arg; this.method = “return”; this.next = “end”; } return ContinueSentinel; }, stop: function() { this.done = true; return this.rval; }};每次 hw.next 的时候,就会修改 next 和 prev 属性的值,当在 generator 函数中 return 的时候会执行 abrupt,abrupt 中又会执行 complete,执行完 complete,因为 this.next = end 的缘故,再执行就会执行 stop 函数。我们来看下 makeInvokeMethod 函数:var ContinueSentinel = {};function makeInvokeMethod(innerFn, self, context) { var state = ‘start’; return function invoke(method, arg) { if (state === ‘completed’) { return { value: undefined, done: true }; } context.method = method; context.arg = arg; while (true) { state = ’executing’; var record = { type: ’normal’, arg: innerFn.call(self, context) }; if (record.type === “normal”) { state = context.done ? ‘completed’ : ‘yield’; if (record.arg === ContinueSentinel) { continue; } return { value: record.arg, done: context.done }; } } };}基本的执行过程就不分析了,我们重点看第三次执行 hw.next() 的时候:第三次执行 hw.next() 的时候,其实执行了this._invoke(“next”, undefined);我们在 invoke 函数中构建了一个 record 对象:var record = { type: “normal”, arg: innerFn.call(self, context)};而在 innerFn.call(self, context) 中,因为 _context.next 为 4 的缘故,其实执行了:_context.abrupt(“return”, ’ending’);而在 abrupt 中,我们又构建了一个 record 对象:var record = {};record.type = ‘return’;record.arg = ’ending’;然后执行了 this.complete(record),在 complete 中,因为 record.type === “return"this.rval = ’ending’;this.method = “return”;this.next = “end”;然后返回了全局对象 ContinueSentinel,其实就是一个全局空对象。然后在 invoke 函数中,因为 record.arg === ContinueSentinel 的缘故,没有执行后面的 return 语句,就直接进入下一个循环。于是又执行了一遍 innerFn.call(self, context),此时 _context.next 为 end, 执行了 _context.stop(), 在 stop 函数中:this.done = true;return this.rval; // this.rval 其实就是 ending所以最终返回的值为:{ value: ’ending’, done: true};之后,我们再执行 hw.next() 的时候,因为 state 已经是 ‘completed’ 的缘故,直接就返回 { value: undefined, done: true}不完整但可用的源码当然这个过程,看文字理解起来可能有些难度,不完整但可用的代码如下,你可以断点调试查看具体的过程:(function() { var ContinueSentinel = {}; var mark = function(genFun) { var generator = Object.create({ next: function(arg) { return this._invoke(“next”, arg); } }); genFun.prototype = generator; return genFun; }; function wrap(innerFn, outerFn, self) { var generator = Object.create(outerFn.prototype); var context = { done: false, method: “next”, next: 0, prev: 0, abrupt: function(type, arg) { var record = {}; record.type = type; record.arg = arg; return this.complete(record); }, complete: function(record, afterLoc) { if (record.type === “return”) { this.rval = this.arg = record.arg; this.method = “return”; this.next = “end”; } return ContinueSentinel; }, stop: function() { this.done = true; return this.rval; } }; generator._invoke = makeInvokeMethod(innerFn, context); return generator; } function makeInvokeMethod(innerFn, context) { var state = “start”; return function invoke(method, arg) { if (state === “completed”) { return { value: undefined, done: true }; } context.method = method; context.arg = arg; while (true) { state = “executing”; var record = { type: “normal”, arg: innerFn.call(self, context) }; if (record.type === “normal”) { state = context.done ? “completed” : “yield”; if (record.arg === ContinueSentinel) { continue; } return { value: record.arg, done: context.done }; } } }; } window.regeneratorRuntime = {}; regeneratorRuntime.wrap = wrap; regeneratorRuntime.mark = mark;})();var _marked = regeneratorRuntime.mark(helloWorldGenerator);function helloWorldGenerator() { return regeneratorRuntime.wrap( function helloWorldGenerator$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return “hello”; case 2: _context.next = 4; return “world”; case 4: return _context.abrupt(“return”, “ending”); case 5: case “end”: return _context.stop(); } } }, _marked, this );}var hw = helloWorldGenerator();console.log(hw.next());console.log(hw.next());console.log(hw.next());console.log(hw.next());ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

October 30, 2018 · 5 min · jiezi

ES6 系列之 Generator 的自动执行

单个异步任务var fetch = require(’node-fetch’);function* gen(){ var url = ‘https://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio);}为了获得最终的执行结果,你需要这样做:var g = gen();var result = g.next();result.value.then(function(data){ return data.json();}).then(function(data){ g.next(data);});首先执行 Generator 函数,获取遍历器对象。然后使用 next 方法,执行异步任务的第一阶段,即 fetch(url)。注意,由于 fetch(url) 会返回一个 Promise 对象,所以 result 的值为:{ value: Promise { <pending> }, done: false }最后我们为这个 Promise 对象添加一个 then 方法,先将其返回的数据格式化(data.json()),再调用 g.next,将获得的数据传进去,由此可以执行异步任务的第二阶段,代码执行完毕。多个异步任务上节我们只调用了一个接口,那如果我们调用了多个接口,使用了多个 yield,我们岂不是要在 then 函数中不断的嵌套下去……所以我们来看看执行多个异步任务的情况:var fetch = require(’node-fetch’);function* gen() { var r1 = yield fetch(‘https://api.github.com/users/github'); var r2 = yield fetch(‘https://api.github.com/users/github/followers'); var r3 = yield fetch(‘https://api.github.com/users/github/repos'); console.log([r1.bio, r2[0].login, r3[0].full_name].join(’\n’));}为了获得最终的执行结果,你可能要写成:var g = gen();var result1 = g.next();result1.value.then(function(data){ return data.json();}).then(function(data){ return g.next(data).value;}).then(function(data){ return data.json();}).then(function(data){ return g.next(data).value}).then(function(data){ return data.json();}).then(function(data){ g.next(data)});但我知道你肯定不想写成这样……其实,利用递归,我们可以这样写:function run(gen) { var g = gen(); function next(data) { var result = g.next(data); if (result.done) return; result.value.then(function(data) { return data.json(); }).then(function(data) { next(data); }); } next();}run(gen);其中的关键就是 yield 的时候返回一个 Promise 对象,给这个 Promise 对象添加 then 方法,当异步操作成功时执行 then 中的 onFullfilled 函数,onFullfilled 函数中又去执行 g.next,从而让 Generator 继续执行,然后再返回一个 Promise,再在成功时执行 g.next,然后再返回……启动器函数在 run 这个启动器函数中,我们在 then 函数中将数据格式化 data.json(),但在更广泛的情况下,比如 yield 直接跟一个 Promise,而非一个 fetch 函数返回的 Promise,因为没有 json 方法,代码就会报错。所以为了更具备通用性,连同这个例子和启动器,我们修改为:var fetch = require(’node-fetch’);function* gen() { var r1 = yield fetch(‘https://api.github.com/users/github'); var json1 = yield r1.json(); var r2 = yield fetch(‘https://api.github.com/users/github/followers'); var json2 = yield r2.json(); var r3 = yield fetch(‘https://api.github.com/users/github/repos'); var json3 = yield r3.json(); console.log([json1.bio, json2[0].login, json3[0].full_name].join(’\n’));}function run(gen) { var g = gen(); function next(data) { var result = g.next(data); if (result.done) return; result.value.then(function(data) { next(data); }); } next();}run(gen);只要 yield 后跟着一个 Promise 对象,我们就可以利用这个 run 函数将 Generator 函数自动执行。回调函数yield 后一定要跟着一个 Promise 对象才能保证 Generator 的自动执行吗?如果只是一个回调函数呢?我们来看个例子:首先我们来模拟一个普通的异步请求:function fetchData(url, cb) { setTimeout(function(){ cb({status: 200, data: url}) }, 1000)}我们将这种函数改造成:function fetchData(url) { return function(cb){ setTimeout(function(){ cb({status: 200, data: url}) }, 1000) }}对于这样的 Generator 函数:function* gen() { var r1 = yield fetchData(‘https://api.github.com/users/github'); var r2 = yield fetchData(‘https://api.github.com/users/github/followers'); console.log([r1.data, r2.data].join(’\n’));}如果要获得最终的结果:var g = gen();var r1 = g.next();r1.value(function(data) { var r2 = g.next(data); r2.value(function(data) { g.next(data); });});如果写成这样的话,我们会面临跟第一节同样的问题,那就是当使用多个 yield 时,代码会循环嵌套起来……同样利用递归,所以我们可以将其改造为:function run(gen) { var g = gen(); function next(data) { var result = g.next(data); if (result.done) return; result.value(next); } next();}run(gen);run由此可以看到 Generator 函数的自动执行需要一种机制,即当异步操作有了结果,能够自动交回执行权。而两种方法可以做到这一点。(1)回调函数。将异步操作进行包装,暴露出回调函数,在回调函数里面交回执行权。(2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。在两种方法中,我们各写了一个 run 启动器函数,那我们能不能将这两种方式结合在一些,写一个通用的 run 函数呢?我们尝试一下:// 第一版function run(gen) { var gen = gen(); function next(data) { var result = gen.next(data); if (result.done) return; if (isPromise(result.value)) { result.value.then(function(data) { next(data); }); } else { result.value(next) } } next()}function isPromise(obj) { return ‘function’ == typeof obj.then;}module.exports = run;其实实现的很简单,判断 result.value 是否是 Promise,是就添加 then 函数,不是就直接执行。return Promise我们已经写了一个不错的启动器函数,支持 yield 后跟回调函数或者 Promise 对象。现在有一个问题需要思考,就是我们如何获得 Generator 函数的返回值呢?又如果 Generator 函数中出现了错误,就比如 fetch 了一个不存在的接口,这个错误该如何捕获呢?这很容易让人想到 Promise,如果这个启动器函数返回一个 Promise,我们就可以给这个 Promise 对象添加 then 函数,当所有的异步操作执行成功后,我们执行 onFullfilled 函数,如果有任何失败,就执行 onRejected 函数。我们写一版:// 第二版function run(gen) { var gen = gen(); return new Promise(function(resolve, reject) { function next(data) { try { var result = gen.next(data); } catch (e) { return reject(e); } if (result.done) { return resolve(result.value) }; var value = toPromise(result.value); value.then(function(data) { next(data); }, function(e) { reject(e) }); } next() })}function isPromise(obj) { return ‘function’ == typeof obj.then;}function toPromise(obj) { if (isPromise(obj)) return obj; if (‘function’ == typeof obj) return thunkToPromise(obj); return obj;}function thunkToPromise(fn) { return new Promise(function(resolve, reject) { fn(function(err, res) { if (err) return reject(err); resolve(res); }); });}module.exports = run;与第一版有很大的不同:首先,我们返回了一个 Promise,当 result.done 为 true 的时候,我们将该值 resolve(result.value),如果执行的过程中出现错误,被 catch 住,我们会将原因 reject(e)。其次,我们会使用 thunkToPromise 将回调函数包装成一个 Promise,然后统一的添加 then 函数。在这里值得注意的是,在 thunkToPromise 函数中,我们遵循了 error first 的原则,这意味着当我们处理回调函数的情况时:// 模拟数据请求function fetchData(url) { return function(cb) { setTimeout(function() { cb(null, { status: 200, data: url }) }, 1000) }}在成功时,第一个参数应该返回 null,表示没有错误原因。优化我们在第二版的基础上将代码写的更加简洁优雅一点,最终的代码如下:// 第三版function run(gen) { return new Promise(function(resolve, reject) { if (typeof gen == ‘function’) gen = gen(); // 如果 gen 不是一个迭代器 if (!gen || typeof gen.next !== ‘function’) return resolve(gen) onFulfilled(); function onFulfilled(res) { var ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); } function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next(ret) { if (ret.done) return resolve(ret.value); var value = toPromise(ret.value); if (value && isPromise(value)) return value.then(onFulfilled, onRejected); return onRejected(new TypeError(‘You may only yield a function, promise ’ + ‘but the following object was passed: “’ + String(ret.value) + ‘”’)); } })}function isPromise(obj) { return ‘function’ == typeof obj.then;}function toPromise(obj) { if (isPromise(obj)) return obj; if (‘function’ == typeof obj) return thunkToPromise(obj); return obj;}function thunkToPromise(fn) { return new Promise(function(resolve, reject) { fn(function(err, res) { if (err) return reject(err); resolve(res); }); });}module.exports = run;co如果我们再将这个启动器函数写的完善一些,我们就相当于写了一个 co,实际上,上面的代码确实是来自于 co……而 co 是什么? co 是大神 TJ Holowaychuk 于 2013 年 6 月发布的一个小模块,用于 Generator 函数的自动执行。如果直接使用 co 模块,这两种不同的例子可以简写为:// yield 后是一个 Promisevar fetch = require(’node-fetch’);var co = require(‘co’);function* gen() { var r1 = yield fetch(‘https://api.github.com/users/github'); var json1 = yield r1.json(); var r2 = yield fetch(‘https://api.github.com/users/github/followers'); var json2 = yield r2.json(); var r3 = yield fetch(‘https://api.github.com/users/github/repos'); var json3 = yield r3.json(); console.log([json1.bio, json2[0].login, json3[0].full_name].join(’\n’));}co(gen);// yield 后是一个回调函数var co = require(‘co’);function fetchData(url) { return function(cb) { setTimeout(function() { cb(null, { status: 200, data: url }) }, 1000) }}function* gen() { var r1 = yield fetchData(‘https://api.github.com/users/github'); var r2 = yield fetchData(‘https://api.github.com/users/github/followers'); console.log([r1.data, r2.data].join(’\n’));}co(gen);是不是特别的好用?ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

October 19, 2018 · 4 min · jiezi

Async:简洁优雅的异步之道

前言在异步处理方案中,目前最为简洁优雅的便是async函数(以下简称A函数)。经过必要的分块包装后,A函数能使多个相关的异步操作如同同步操作一样聚合起来,使其相互间的关系更为清晰、过程更为简洁、调试更为方便。它本质是Generator函数的语法糖,通俗的说法是使用G函数进行异步处理的增强版。尝试学习A函数必须有Promise基础,最好还了解Generator函数,有需要的可查看延伸小节。为了直观的感受A函数的魅力,下面使用Promise和A函数进行了相同的异步操作。该异步的目的是获取用户的留言列表,需要分页,分页由后台控制。具体的操作是:先获取到留言的总条数,再更正当前需要显示的页数(每次切换到不同页时,总数目可能会发生变化),最后传递参数并获取到相应的数据。let totalNum = 0; // Total comments number.let curPage = 1; // Current page index.let pageSize = 10; // The number of comment displayed in one page.// 使用A函数的主代码。async function dealWithAsync() { totalNum = await getListCount(); console.log(‘Get count’, totalNum); if (pageSize * (curPage - 1) > totalNum) { curPage = 1; } return getListData();}// 使用Promise的主代码。function dealWithPromise() { return new Promise((resolve, reject) => { getListCount().then(res => { totalNum = res; console.log(‘Get count’, res); if (pageSize * (curPage - 1) > totalNum) { curPage = 1; } return getListData() }).then(resolve).catch(reject); });}// 开始执行dealWithAsync函数。// dealWithAsync().then(res => {// console.log(‘Get Data’, res)// }).catch(err => {// console.log(err);// });// 开始执行dealWithPromise函数。// dealWithPromise().then(res => {// console.log(‘Get Data’, res)// }).catch(err => {// console.log(err);// });function getListCount() { return createPromise(100).catch(() => { throw ‘Get list count error’; });}function getListData() { return createPromise([], { curPage: curPage, pageSize: pageSize, }).catch(() => { throw ‘Get list data error’; });}function createPromise( data, // Reback data params = null, // Request params isSucceed = true, timeout = 1000,) { return new Promise((resolve, reject) => { setTimeout(() => { isSucceed ? resolve(data) : reject(data); }, timeout); });}对比dealWithAsync和dealWithPromise两个简单的函数,能直观的发现:使用A函数,除了有await关键字外,与同步代码无异。而使用Promise则需要根据规则增加很多包裹性的链式操作,产生了太多回调函数,不够简约。另外,这里分开了每个异步操作,并规定好各自成功或失败时传递出来的数据,近乎实际开发。1 登堂1.1 形式A函数也是函数,所以具有普通函数该有的性质。不过形式上有两点不同:一是定义A函数时,function关键字前需要有async关键字(意为异步),表示这是个A函数。二是在A函数内部可以使用await关键字(意为等待),表示会将其后面跟随的结果当成异步操作并等待其完成。以下是它的几种定义方式。// 声明式async function A() {}// 表达式let A = async function () {};// 作为对象属性let o = { A: async function () {}};// 作为对象属性的简写式let o = { async A() {}};// 箭头函数let o = { A: async () => {}};1.2 返回值执行A函数,会固定的返回一个Promise对象。得到该对象后便可监设置成功或失败时的回调函数进行监听。如果函数执行顺利并结束,返回的P对象的状态会从等待转变成成功,并输出return命令的返回结果(没有则为undefined)。如果函数执行途中失败,JS会认为A函数已经完成执行,返回的P对象的状态会从等待转变成失败,并输出错误信息。// 成功执行案例A1().then(res => { console.log(‘执行成功’, res); // 10});async function A1() { let n = 1 * 10; return n;}// 失败执行案例A2().catch(err => { console.log(‘执行失败’, err); // i is not defined.});async function A2() { let n = 1 * i; return n;}1.3 await只有在A函数内部才可以使用await命令,存在于A函数内部的普通函数也不行。引擎会统一将await后面的跟随值视为一个Promise,对于不是Promise对象的值会调用Promise.resolve()进行转化。即便此值为一个Error实例,经过转化后,引擎依然视其为一个成功的Promise,其数据为Error的实例。当函数执行到await命令时,会暂停执行并等待其后的Promise结束。如果该P对象最终成功,则会返回成功的返回值,相当将await xxx替换成返回值。如果该P对象最终失败,且错误没有被捕获,引擎会直接停止执行A函数并将其返回对象的状态更改为失败,输出错误信息。最后,A函数中的return x表达式,相当于return await x的简写。// 成功执行案例A1().then(res => { console.log(‘执行成功’, res); // 约两秒后输出100。});async function A1() { let n1 = await 10; let n2 = await new Promise(resolve => { setTimeout(() => { resolve(10); }, 2000); }); return n1 * n2;}// 失败执行案例A2().catch(err => { console.log(‘执行失败’, err); // 约两秒后输出10。});async function A2() { let n1 = await 10; let n2 = await new Promise((resolve, reject) => { setTimeout(() => { reject(10); }, 2000); }); return n1 * n2;}2 入室2.1 继发与并发对于存在于JS语句(for, while等)的await命令,引擎遇到时也会暂停执行。这意味着可以直接使用循环语句处理多个异步。以下是处理继发的两个例子。A函数处理相继发生的异步尤为简洁,整体上与同步代码无异。// 两个方法A1和A2的行为结果相同,都是每隔一秒输出10,输出三次。async function A1() { let n1 = await createPromise(); console.log(‘N1’, n1); let n2 = await createPromise(); console.log(‘N2’, n2); let n3 = await createPromise(); console.log(‘N3’, n3);}async function A2() { for (let i = 0; i< 3; i++) { let n = await createPromise(); console.log(‘N’ + (i + 1), n); }}function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(10); }, 1000); });}接下来是处理并发的三个例子。A1函数使用了Promise.all生成一个聚合异步,虽然简单但灵活性降低了,只有都成功和失败两种情况。A3函数相对A2仅仅为了说明应该怎样配合数组的遍历方法使用async函数。重点在A2函数的理解上。A2函数使用了循环语句,实际是继发的获取到各个异步值,但在总体的时间上相当并发(这里需要好好理解一番)。因为一开始创建reqs数组时,就已经开始执行了各个异步,之后虽然是逐一继发获取,但总花费时间与遍历顺序无关,恒等于耗时最多的异步所花费的时间(不考虑遍历、执行等其它的时间消耗)。// 三个方法A1, A2和A3的行为结果相同,都是在约一秒后输出[10, 10, 10]。async function A1() { let res = await Promise.all([createPromise(), createPromise(), createPromise()]); console.log(‘Data’, res);}async function A2() { let res = []; let reqs = [createPromise(), createPromise(), createPromise()]; for (let i = 0; i< reqs.length; i++) { res[i] = await reqs[i]; } console.log(‘Data’, res);}async function A3() { let res = []; let reqs = [9, 9, 9].map(async (item) => { let n = await createPromise(item); return n + 1; }); for (let i = 0; i< reqs.length; i++) { res[i] = await reqs[i]; } console.log(‘Data’, res);}function createPromise(n = 10) { return new Promise(resolve => { setTimeout(() => { resolve(n); }, 1000); });}2.2 错误处理一旦await后面的Promise转变成rejected,整个async函数便会终止。然而很多时候我们不希望因为某个异步操作的失败,就终止整个函数,因此需要进行合理错误处理。注意,这里所说的错误不包括引擎解析或执行的错误,仅仅是状态变为rejected的Promise对象。处理的方式有两种:一是先行包装Promise对象,使其始终返回一个成功的Promise。二是使用try.catch捕获错误。// A1和A2都执行成,且返回值为10。A1().then(console.log);A2().then(console.log);async function A1() { let n; n = await createPromise(true); return n;}async function A2() { let n; try { n = await createPromise(false); } catch (e) { n = e; } return n;}function createPromise(needCatch) { let p = new Promise((resolve, reject) => { reject(10); }); return needCatch ? p.catch(err => err) : p;}2.3 实现原理前言中已经提及,A函数是使用G函数进行异步处理的增强版。既然如此,我们就从其改进的方面入手,来看看其基于G函数的实现原理。A函数相对G函数的改进体现在这几个方面:更好的语义,内置执行器和返回值是Promise。更好的语义。G函数通过在function后使用来标识此为G函数,而A函数则是在function前加上async关键字。在G函数中可以使用yield命令暂停执行和交出执行权,而A函数是使用await来等待异步返回结果。很明显,async和await更为语义化。// G函数function request() { let n = yield createPromise();}// A函数async function request() { let n = await createPromise();}function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(10); }, 1000); });}内置执行器。调用A函数便会一步步自动执行和等待异步操作,直到结束。如果需要使用G函数来自动执行异步操作,需要为其创建一个自执行器。通过自执行器来自动化G函数的执行,其行为与A函数基本相同。可以说,A函数相对G函数最大改进便是内置了自执行器。// 两者都是每隔一秒钟打印出10,重复两次。// A函数A();async function A() { let n1 = await createPromise(); console.log(n1); let n2 = await createPromise(); console.log(n2);}// G函数,使用自执行器执行。spawn(G);function* G() { let n1 = yield createPromise(); console.log(n1); let n2 = yield createPromise(); console.log(n2);}function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); });}function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(10); }, 1000); });}延伸ES6精华:Promise Generator:JS执行权的真实操作者 ...

September 1, 2018 · 4 min · jiezi