关于异步:新技能码住在前端表格中花式使用异步函数的奥义

背景60年代时,操作系统中独立运行的单元通常是过程。但随着计算机技术的倒退,人们发现在过程运行过程中,创立、撤销与切换都要花费较大的时空开销。 到了80年代为了解决这一问题,呈现了更小的独立运行根本单位——线程。 操作系统把 CPU 解决工夫划分成许多更小的工夫片,在每一个独立工夫片执行一个线程的指令,到下一个工夫片继续执行下一线程的指令,各线程轮流执行,因为每一个工夫片工夫都比拟短,所有线程都会运行,对于使用者而言就如同所有线程在同时进行。最终达到的成果就是在编程时能够创立多个线程,同一时间运行,各线程能够"并行"运行,以实现不同的工作。 这时新的问题也呈现了,在独自线程的运行模式之下,一段代码调用另一段代码时,只能采纳同步调用,只有以后代码执行实现返回后果之后,调用能力持续往下执行。用一个例子就是当初只有一个水槽,一匹马想喝水只能等上一匹马走了能力持续喝。 而有了多线程的反对,能够采纳异步函数的调用,这个问题就迎刃而解了。 异步函数原理介绍程序中会有很多内容,计算内容简单、渲染内容繁多,在处理过程中须要破费比拟多的工夫。当某个模块A调用了模块B的解决内容时,这时模块B中的内容就须要一些工夫解决,此时模块A如果不停地期待,就会重大影响程序性能。在理论状况中,就比方在前端页面中须要进行在线填报的数据处理,须要对数据内容进行计算后放入表格中展现,这是因为计算并未实现,页面内容也不显示,给用户带来的感觉就是内容都点击运行了,然而页面迟迟没有任何反馈。 呈现了异步函数的调用之后,此时执行的模块A和模块B别离属于不同的线程。 在异步调用中,模块A不须要等到模块B返回内容,就能够继续执行后续代码。 模块B中的内容执行结束后,会告诉模块A:我这边处理完毕,你记得解决后续内容。 借助异步调用,能够把刚刚咱们提到的前端页面中显示问题进行优化:把整个初始化解决放进一个独自线程,主线程启动此线程后接着往下走,让主窗口霎时显示进去。等考虑须要进行的操作的内容时,数据计算解决就曾经在暗中处理完毕;程序开始稳固运行当前,异步调用还能够进一步优化人机交互的过程。用户点击鼠标时进行操作的时候,操作内容比拟费时,点击后零碎没有立马作出回应,这会让用户的应用体验很蹩脚。将更费时、速度更慢的操作内容转为异步调用,让主线程随时恭候下一条音讯,这样用户的鼠标操作动作响应速度更快,应用体验天然大大晋升。 实际:专家用户的花式应用实例演示咱们用一个简略的例子,看看在前端电子表格单元格计算中,如何应用异步函数。 var ServerDecode = function () {};ServerDecode.prototype = new GC.Spread.CalcEngine.Functions.AsyncFunction("DECODE", 1, 255);ServerDecode.prototype.evaluateAsync = function (context, arg1) { $.get("decode/" + arg1, function (data, status) { context.setAsyncResult(data); });};spread.addCustomFunction(new ServerDecode());sheet.setFormula(0, 1, '=DECODE(A1)');在这个算法中咱们将设定的计算解析办法局部放在服务器上,办法名称叫DECODE 下一步将参数用jquery.get申请发送到服务器中,而后获取申请内容后实现设置 而后将整个异步函数注册进入Spread中 最初在B1单元格中,输出DECODE(A1) 这样当A1单元格内容发生变化的时候,B1就会依据咱们设定的计算规定重算成对应内容 异步函数的花式应用工具总在不同人手中被挖掘出各种各样的用法,而在去年冬天咱们就收到了用户反馈的异步函数的各种微妙应用形式。 他们应用异步函数的参数组合成了一个SQL,发送给数据库进行数据查问,并在查问完结后显示查问后果。后果所有正确,然而却呈现了一个小问题。 在应用过程中,用户发现查问在整个过程中超过了 四次 ,询问咱们是否是公式出错? 咱们当即发展问题排查,在查看源代码的过程中咱们发现,在最早实现这个性能的时候为了强调数据重要性,当同一个公式中呈现多个异步函数调用时,再次计算下一个内容时咱们还会再计算一次曾经计算过的异步函数的内容。 没想到用户的确会这样应用异步函数,这一部分内容随后也进行整体调整。现已调整为每次调用只计算一次异步函数。 有了这次经验,再遇到用户对异步函数的其余花里胡哨的用法,咱们就见怪不怪了。 果不其然,没多久又收到了其余用户的花式应用反馈。 这一次用户应用异步函数从服务器获取以后服务名,并在SpreadJS显示进去。 咱们发现这个用户还在其中增加了格局字符串,用以获取用户的二维码。同时在这里还设置了条件格局,如果用户没有登录会有报错提醒。 这个例子内容虽短,但在这里用户将异步函数、条件、格局还有格局字符串三个性能都联合在一起应用。 总结以上就是咱们全副对异步函数诞生背景和原理,以及在前端电子表格中异步函数的应用和各种神仙用户的花式应用,到本节对于电子表格计算原理的全部内容就曾经介绍结束。 感觉内容不错点个赞再走吧~ ...

November 17, 2021 · 1 min · jiezi

关于异步:处理可能超时的异步操作

自从 ECMAScript 的 Promise ES2015 和 async/await ES2017 个性公布当前,异步在前端界曾经成为特地常见的操作。异步代码和同步代码在解决问题程序上会存在一些差异,编写异步代码须要领有跟编写同步代码不同的“意识”,为此我还专门写了一篇「异步编程须要“意识”」,不过看的人不多,可能的确“无趣”。 本文要聊的问题可能依然“无趣”,但很事实 —— 如果一段代码久久不能执行实现,会怎么样? 如果这是同步代码,咱们会看到一种叫做“无响应”的景象,或者艰深地说 —— “死掉了”;然而如果是一段异步代码呢?可能咱们等不到后果,但别的代码仍在持续,就如同这件事件没有产生个别。 当然事件并不是真的没产生,只不过在不同的状况下会产生不同的景象。比方有加载动画的页面,看起来就是始终在加载;又比方应该进行数据更新的页面,看不到数据变动;再比方一个对话框,怎么也关不掉 …… 这些景象咱们统称为 BUG。但也有一些时候,某个异步操作过程并没有“回显”,它就默默地死在那里,没有人晓得,待页面刷新之后,就连一点陈迹都不会留下。 当然,这不是小说,咱们得聊点“闲事”。 Axios 自带超时解决应用 Axios 进行 Web Api 调用就是一种常见的异步操作过程。通常咱们的代码会这样写: try { const res = await axios.get(url, options); // TODO 失常进行后续业务} catch(err) { // TODO 进行容错解决,或者报错}这段代码个别状况下都执行良好,直到有一天用户埋怨说:怎么等了半天没反馈? 而后开发者意识到,因为服务器压力增大,这个申请曾经很难刹时响应了。思考到用户的感触,加了一个 loading 动画: try { showLoading(); const res = await axios.get(url, options); // TODO 失常业务} catch (err) { // TODO 容错解决} finally { hideLoading();}然而有一天,有用户说:“我等了半个小时,竟然始终在那转圈圈!”于是开发者意识到,因为某种原因,申请被卡死了,这种状况下应该重发申请,或者间接报告给用户 —— 嗯,得加个超时查看。 ...

November 16, 2021 · 2 min · jiezi

关于异步:JavaScript-异步编程

残缺高频题库仓库地址:https://github.com/hzfe/awesome-interview 残缺高频题库浏览地址:https://febook.hzfe.org/ 相干问题JavaScript 异步编程计划有哪些JavaScript 异步编程计划各有什么优缺点答复关键点阻塞 事件循环 回调函数 JavaScript 是一种同步的、阻塞的、单线程的语言,一次只能执行一个工作。但浏览器定义了非同步的 Web APIs,将回调函数插入到事件循环,实现异步工作的非阻塞执行。常见的异步计划有异步回调、定时器、公布/订阅模式、Promise、生成器 Generator、async/await 以及 Web Worker。 知识点深刻1. 异步回调异步回调函数作为参数传递给在后盾执行的其余函数。当后盾运行的代码完结,就调用回调函数,告诉工作曾经实现。具体示例如下: // 第一个参数是监听的事件类型,第二个就是事件产生时调用的回调函数。btn.addEventListener("click", () => { console.log("You clicked me!"); const pElem = document.createElement("p"); pElem.textContent = "hello, hzfe."; document.body.appendChild(pElem);});异步回调是编写和解决 JavaScript 异步逻辑的最罕用形式,也是最根底的异步模式。然而随着 JavaScript 的倒退,异步回调的问题也不容忽视: 回调表白异步流程的形式是非线性的,非程序的,了解老本较高。回调会受到管制反转的影响。因为回调的控制权在第三方(如 Ajax),由第三方来调用回调函数,无奈确定调用是否合乎预期。多层嵌套回调会产生回调天堂(callback hell)。2. 定时器:setTimeout/setInterval/requestAnimationFrame这三个都能够用异步形式运行代码。次要特色如下: setTimeout:通过任意工夫后运行函数,递归 setTimeout 在 JavaScript 线程不阻塞情的况下可保障执行距离雷同。setInterval:容许反复执行一个函数,并设置工夫距离,不能保障执行距离雷同。requestAnimationFrame:以以后浏览器/零碎的最佳帧速率反复且高效地运行函数的办法。个别用于解决动画成果。setInterval 会按设定的工夫距离固定调用,其中 setInterval 外面的代码的执行工夫也蕴含在内,所以理论距离小于设定的工夫距离。而递归 setTimeout 是调用时才开始算工夫,能够保障屡次递归调用时的距离雷同。 如果以后 JavaScript 线程阻塞,轮到的 setInterval 无奈执行,那么本次工作就会被抛弃。而 setTimeout 被阻塞后不会被抛弃,等到闲暇时会继续执行,但无奈保障执行距离。 3. 公布/订阅模式(publish-subscribe pattern)公布/订阅模式是一种对象间一对多的依赖关系,当一个对象的状态产生扭转时,所有依赖于它的对象都将失去状态扭转的告诉。 下面异步回调的例子也是一个公布/订阅模式(publish-subscribe pattern)的实现。订阅 btn 的 click 事件,当 btn 被点击时向订阅者发送这个音讯,执行对应的操作。 ...

October 30, 2021 · 2 min · jiezi

关于异步:以两种异步模型应用案例深度解析Future接口

摘要:本文以理论案例的模式剖析了两种异步模型,并从源码角度深度解析Future接口和FutureTask类。本文分享自华为云社区《【精通高并发系列】两种异步模型与深度解析Future接口(一)!》,作者:冰 河 。 本文以理论案例的模式剖析了两种异步模型,并从源码角度深度解析Future接口和FutureTask类,心愿大家踏下心来,关上你的IDE,跟着文章看源码,置信你肯定播种不小! 一、两种异步模型在Java的并发编程中,大体上会分为两种异步编程模型,一类是间接以异步的模式来并行运行其余的工作,不须要返回工作的后果数据。一类是以异步的模式运行其余工作,须要返回后果。 1.无返回后果的异步模型无返回后果的异步工作,能够间接将工作丢进线程或线程池中运行,此时,无奈间接取得工作的执行后果数据,一种形式是能够应用回调办法来获取工作的运行后果。 具体的计划是:定义一个回调接口,并在接口中定义接管工作后果数据的办法,具体逻辑在回调接口的实现类中实现。将回调接口与工作参数一起放进线程或线程池中运行,工作运行后调用接口办法,执行回调接口实现类中的逻辑来处理结果数据。这里,给出一个简略的示例供参考。 定义回调接口package io.binghe.concurrent.lab04;/** * @author binghe * @version 1.0.0 * @description 定义回调接口 */public interface TaskCallable<T> { T callable(T t);}便于接口的通用型,这里为回调接口定义了泛型。 定义工作后果数据的封装类package io.binghe.concurrent.lab04;import java.io.Serializable;/** * @author binghe * @version 1.0.0 * @description 工作执行后果 */public class TaskResult implements Serializable { private static final long serialVersionUID = 8678277072402730062L; /** * 工作状态 */ private Integer taskStatus; /** * 工作音讯 */ private String taskMessage; /** * 工作后果数据 */ private String taskResult; //省略getter和setter办法 @Override public String toString() { return "TaskResult{" + "taskStatus=" + taskStatus + ", taskMessage='" + taskMessage + '\'' + ", taskResult='" + taskResult + '\'' + '}'; }}创立回调接口的实现类回调接口的实现类次要用来对工作的返回后果进行相应的业务解决,这里,为了不便演示,只是将后果数据返回。大家须要依据具体的业务场景来做相应的剖析和解决。 ...

July 30, 2021 · 8 min · jiezi

关于异步:处理实时搜索-异步数据问题

最近在写我的项目的过程中,遇到一个问题。就是实时搜寻,然而异步数据返回的工夫不统一,导致,搜寻的后果和文本其实并不能齐全匹配解法一在申请胜利后,判断参数是否和搜寻条件统一,如果统一,才 setState。这种解法 解法二应用申请库的 cancel 办法 axios cancel: https://github.com/axios/axio... umi-request cancel: https://github.com/umijs/umi-... 很显著,2 的解法要比 1 的解法高级很多

December 18, 2020 · 1 min · jiezi

关于异步:问世间异步为何物

异步定义对于异步的定义,网上有很多不同的模式,然而归根结底中心思想是不变的。无论是在http申请调用的层面,还是在cpu内核态和用户态传输数据的层面,异步这个行为针对的是调用方: 一个能够无需期待被调用方的返回值就让操作持续进行的办法在少数程序员的概念中个别是指线程解决的层面: 异步是计算机多线程的异步解决。与同步解决绝对,异步解决不必阻塞以后线程来期待解决实现,而是容许后续操作,直至其它线程将解决实现,并回调告诉此线程 能够这样艰深的了解,异步次要解决的问题是不阻塞调用方,调用方这里能够是http申请的发起者,也能够是一个线程。 但此处须要明确的是:异步与多线程与并行不是同一个概念。 CPU密集型操作我听有的同学说,异步解决的是IO密集型的操作,菜菜感觉是不精确的。异步同样能够解决CPU密集型操作,只不过场景无限而已。有一个前提:利用异步解决CPU密集型操作要求以后运行环境反对多线程才行,比方javascript这个语言,实质上它的运行环境是单线程的,所以对于CPU密集型操作,javascript会显得力不从心。 异步解决CPU密集操作个别状况下产生在同过程中,为什么这么说呢,如果产生在不同机器或者不同过程在很多状况下曾经属于IO密集型的范畴了。这里顺便揭示一下:IO操作可不单单是指磁盘的操作,所有有输出/输入(Input/Output)操作的都能够泛称为IO。 举个栗子吧:在一个带有UI的软件上点击一个按钮,UI线程会产生操作行为,如果UI线程在执行过程中有一个计算比拟耗时的操作(你能够设想成计算1--999999999的和),UI线程在同步操作的状况下会始终期待计算结果,在计算结束之后才会继续执行残余操作,在期待的这个过程中,出现给用户的状况就是UI卡住了,俗称假死了,带给用户的体验是十分不好的。这种状况下,咱们能够新启动一个线程去执行这个耗时的操作,当执行结束,利用某种告诉机制来告诉原来线程,以便原来线程持续本人的操作。 启动新线程执行CPU密集型操作利用的其实就是多线程的劣势,如果是单核CPU,其实这种劣势并不显著IO密集型操作异步的劣势在IO密集型操作中体现的酣畅淋漓,无论是读取一个文件还是发动一个网络申请,菜菜的倡议是尽量应用异步。这里首先遍及一个小常识:其实每个外设设施都有本人的处理器,比方磁盘,所以每个外设设施都能够解决本人相应的申请操作。然而解决外设设施信息的速度和cpu的执行速度来比拟有着天壤之别。 上图展现了不同的 IO 操作所占用的 CPU 时钟周期,在计算机中,CPU 的运算速度最快,以其的运算速度为基准,时钟周期为1。其次是一级缓存、二级缓存和内存,硬盘和网络最慢,它们所破费的时钟周期和内存所破费的时钟周期差距在五位数以上,更不必提跟 CPU 和一级缓存、二级缓存的差距了。 因为速度的差距,所以简直所有的IO操作都举荐应用异步。比方当读取磁盘一个文件的时候,同步状态下以后线程在期待读取的后果,这个线程闲置的工夫简直能够用蛋疼来形容。所以古代的简直所有的出名第三方的操作都是异步操作,尤其以Redis,Nodejs 为代表的单线程运行环境令人另眼相看。 当初是微服务流行的时代,UI往往一个简略的按钮操作,其实在后台程序可能调用了几个甚至更多的微服务接口(对于微服务这里不开展),如果程序是同步操作的话,那响应工夫是这些服务接口响应工夫的和,然而如果采纳的是异步操作,调用方能够在霎时把调用服务接口的操作发送进来,线程能够继续执行下边代码或者期待所有的服务接口返回值也能够。最差的状况下,接口的响应工夫为最慢的那个服务接口响应工夫,这有点相似于木桶效应。 异步的回调通过以上介绍,咱们肯定要记住一个知识点:异步须要回调机制。异步操作之所以能在执行后果实现之后继续执行上面程序齐全归功于回调,这也是所有异步场景的外围所在,前到js的异步回调,后到cpu内核空间copy数据到用户空间实现告诉 等等异步场景,回调无处不在。说道回调大部分语言都是注册一个回调函数,比方js会把回调的办法注册到执行的队列,c#会把回调注册到IOCP。这里延长一下,在很多零碎里,很多IO网络模型其实是属于同步领域的,比方多路复用技术,真正异步非阻塞的举荐windows下的IOCP。 当初很多古代语言都反对更优良的回调形式,比方js和c# 当初都反对async 和await形式来进行异步操作。 据说windows下的IOCP才是真正的异步非阻塞模型,求留言区验证! 异步的特点劣势异步操作毋庸额定的线程累赘,应用回调的形式进行后续解决,在设计良好的状况下,处理函数能够不用应用共享变量(即便无奈齐全不必,最起码能够缩小 共享变量的数量),缩小了死锁的可能。线程数量的缩小,缩小了线程上下文在cpu切换的开销。微服务环境(调用多个服务接口的状况下)放慢了下层接口的响应工夫,意味着减少了下层接口的吞吐量劣势异步操作传统的做法都是通过回调函数来实现,与同步的思维有些差别,而且难以调试如果以后环境有操作程序的要求,异步操作为了保障执行的程序须要做额定的工作因为少数状况下异步的回调过程中的执行线程并非原来的线程,所以在捕捉异样,上下文传递等方面须要做非凡解决,特地是不同线程共享代码或共享数据时容易出问题。写在最初在并发量较小的状况下,阻塞式 IO和异步IO的差距可能不是那么显著,但随着并发量的减少,异步IO的劣势将会越来越大,吞吐率和性能上的差距也会越来越显著。在压力比拟小的状况下,个别异步申请的响应工夫大于同步申请的响应工夫,因为异步的回调也是须要工夫的在大并发的状况下,采纳异步调用的程序所用线程数要远远小于同步调用程序所用的线程数,cpu使用率也一样(因为防止了太多线程上下文切换的老本)为了零碎性能,不要让任何设施停下来劳动更多精彩文章 分布式大并发系列架构设计系列趣学算法和数据结构系列设计模式系列

September 24, 2020 · 1 min · jiezi

关于异步:阻塞与非阻塞-同步与异步

同步与异步我了解同步与异步是指被调用者一端的,同步与异步在意的是音讯通信机制,例如程序调用了一个办法,同步与异步指的是该办法会以怎么样的模式进行返回,同步:就是在该办法没有运行出后果前都不会返回,但一返回就有后果;异步:就是在该办法运行后就会返回,然而没有返回后果,会通过回调的机制返回运行后果;例子:若你在网上买了一个商品.然而呈现了售后问题,你给客服发了音讯询问处理结果,若是同步模式,客服和你说他去询问一下售后工程师处理结果,而后你就始终等啊等,直到他们探讨出后果告知你才算完结;而若是异步模式,客服和你说他去询问一下售后工程师处理结果,有了后果再给你回电话,而后此次通话就到此结束,第二天早上他给你打电话告知后果. 阻塞与非阻塞我了解阻塞与非阻塞是在调用者一端的,例如程序调用了一个办法,阻塞与非阻塞指的是以后程序在期待调用后果时的状态,若肯定要等到返回调用后果该线程才会返回,等于将线程挂起,这即是阻塞;若调用不会立即失去后果,该次调用也不会妨碍线程的运行,即是非阻塞;例子: 若你在网上买了一个商品.然而呈现了售后问题,你给客服发了音讯询问处理结果,若你是阻塞式的,不论客服有没有分割到售后能不能给你后果,你都会始终期待;而若你是非阻塞的,当你询问客服后,不论她有没有立即回复,你就先去忙本人的事件,然而也会偶然回来查看后果.

September 18, 2020 · 1 min · jiezi

关于异步:Spring-AOP-异步操作

Spring业务的异步实现在基于注解形式的配置中,借助@EnableAsync注解进行异步启动申明(1)在Spring Boot我的项目中,在启动类Application上利用@EnableAsync注解(2)在须要异步执行的业务办法上,应用@Async办法进行异步申明如果须要获取业务层异步办法的执行后果,AsyncResult对象能够对异步办法的执行后果进行封装,如果外界须要异步办法后果时,能够通过Future对象的get办法获取后果 Spring 自定义异步池import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import java.util.concurrent.ThreadFactory;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicLong;public class TestThreadPool { public static void main(String[] args) { BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<Runnable>(1);//线程容量:阻塞队列数+最大线程数 //如果默认线程名不满足你业务须要,能够本人创立线程工厂,而后定义线程名 ThreadFactory threadFactory=new ThreadFactory() { //AtomicLong 提供了一种线程平安的自增或自减算法对一个整数进行计算 private AtomicLong al=new AtomicLong(1); @Override public Thread newThread(Runnable r) { return new Thread(r, "db-thread-"+al.getAndIncrement()); } }; //1.构建一个线程池 ThreadPoolExecutor tp=new ThreadPoolExecutor( 2,//corePoolSize 外围线程数(当应用池对象执行工作时,池中线程没有达到corePoolSize设置定值时,每来一个新的工作都会创立一个新的线程) 3,//maximumPoolSize 最大线程(当外围线程都在忙,队列也满,再来新的工作则创立新线程) 60,//keepAliveTime 最大闲暇工夫 TimeUnit.SECONDS, //unit 工夫单位 workQueue,//workQueue阻塞式队列 threadFactory,//创立线程的工厂 new ThreadPoolExecutor.CallerRunsPolicy());//工作拒绝执行策略,这里抉择了CallerRunsPolicy对象(示意最初由调用者线程执行) //2.启动池中线程执行工作 tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 01"); try{Thread.sleep(5000);}catch (Exception e) {} } }); tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 02"); try{Thread.sleep(5000);}catch (Exception e) {} } }); tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 03"); try{Thread.sleep(5000);}catch (Exception e) {} } }); tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 04"); try{Thread.sleep(5000);}catch (Exception e) {} } }); tp.execute(new Runnable() { @Override public void run() { String tName=Thread.currentThread().getName(); System.out.println(tName+" execute task 05"); try{Thread.sleep(5000);}catch (Exception e) {} } }); }}执行后果 ...

September 17, 2020 · 1 min · jiezi

关于异步:宏任务macroTask和微任务microTask

宏工作:setTimeout,setInterval,ajax,DOM事件微工作:Promise,async/await微工作执行机会比宏工作早console.log(100)//宏工作setTimeout(()=>{ console.log(200)})//微工作Promise.resolve.then(()=>{ console.log(300)})console.log(400)//打印程序:100,400,300,200 为什么微工作比宏工作执行的早宏工作:DOM渲染后触发,如setTimeOut微工作:DOM渲染前触发,如Promise宏工作和微工作的基本区别宏工作是浏览器规定的;微工作是es6语法规定的;微工作期待机会放在micro task queue中,首先执行同步代码,当代码执行实现,call stack清空之后,执行以后的微工作,再尝试DOM渲染,最初触发event loop机制;

September 8, 2020 · 1 min · jiezi

iOS知识梳理-异步编程-coobjc学习

这几年异步编程是个比较热门的话题。 今天我们在iOS平台下简单聊聊异步编程和coobjc。 首先要回答一个问题,我们为什么需要异步编程? 早年的时候,大家都很习惯于开一个线程去执行耗时任务,即使这个耗时任务并非CPU密集型任务,比如一个同步的IO或网络调用。但发展到今日,大家对这种场景应该使用异步而非子线程的结论应当没什么疑问。开线程本身开销相对比较大,并且多线程编程动不动要加锁,很容易出现crash或更严重的性能问题。而iOS平台,系统API有不少就是这种不科学的同步耗时调用,并且GCD的API算是很好用的线程封装,这导致iOS平台下很容易滥用多线程引发各种问题。 总而言之,原则上,网络、IO等很多不耗CPU的耗时操作都应该优先使用异步来解决。 再来看异步编程的方案,iOS平台下常用的就是delegate和block回调。delegate导致逻辑的割裂,并且使用场景比较注重于UI层,对大多数异步场景算不上好用。 而block回调语法同样有一些缺陷。最大的问题就是回调地狱: [NSURLConnection sendAsynchronousRequest:rq queue:nil completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) { if (connectionError) { if (callback) { callback(nil, nil,connectionError); } } else{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; NSString *imageUrl = dict[@"image"]; [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]] queue:nil completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ if (connectionError) { callback(nil, dict,connectionError); } else{ UIImage *image = [[UIImage alloc] initWithData:data]; if (callback) { (image, dict, nil); } } }); }]; }); } }]不过iOS开发中好像并没有觉得很痛,至少没有前端那么痛。可能是因为我们实际开发中对比较深的回调会换成用delegate或notificaiton机制。但这种混杂的使用对代码质量是个挑战,想要保证代码质量需要团队做很多约束并且有很高的执行力,这其实很困难。 ...

November 5, 2019 · 3 min · jiezi

深入理解React中的setState

组件的状态是一种保存、处理和使用给定组件内部信息的方法,并允许你实现其自身的逻辑。状态本身其实是JavaScript中一个简单的对象(Plain Old Java[Script] Object),并且改变它是使组件重新进行渲染的几种方法之一。 这是React背后最基本的思路之一,但是它(状态)有一些使用起来很棘手的属性,可能会导致应用程序出现意外行为。 更新状态组件中的构造函数是唯一一个你可以直接写this.state的地方,而在其他地方你应该使用this.setState,setState将接受最终合并到组件当前状态的一个对象或方法作为参数。 虽然技术上可以通过直接写入this.state来改变状态,但它不会导致组件使用新数据重新渲染,并且通常会导致状态的不一致。 setState是异步的setState导致协调(重新渲染组件树的过程)的事实是基于下一个属性 — setState是异步的。这允许我们在单个范围内多次调用setState,而不是触发不需要重新渲染整个组件树。 这就是为什么在更新后没有在状态中看到新值的原因。 // assuming this.state = { value: 0 }this.setState({ value: 1});console.log(this.state.value); // 0React还会尝试将setState分组调用或批量调用到一个回调中,这会导致我们第一次“陷阱”。 // assuming this.state = { value: 0 };this.setState({ value: this.state.value + 1});this.setState({ value: this.state.value + 1});this.setState({ value: this.state.value + 1});上面所有的调用过程结束后,this.state.value的值是1,而不是我们所期望的3。为了解决这个问题 … setState接受一个方法作为它的参数如果你在setState中传入一个函数作为第一个参数,React将以 at-call-time-current状态来调用它,并期望你返回一个对象来合并到状态中。所以更新我们以上的代码: // assuming this.state = { value: 0 };this.setState((state) => ({ value: state.value + 1}));this.setState((state) => ({ value: state.value + 1}));this.setState((state) => ({ value: state.value + 1}));最终的结果将如我们所期望的this.state.value = 3,记住在将状态更新为值时始终使用此语法,该值是根据以前的状态计算的! ...

July 26, 2019 · 1 min · jiezi

将前后端交互同步化本篇封装了一下微信小程序的请求

今天自己写小程序的时候,近乎被异步搞到崩溃,不停地嵌套回调(我知道 await 和 promise,但是我嫌promise写起来跟裹脚布似的,而await我怕有兼容性问题也从来没有试过)言归正传,将小程序的异步调用变为同步(以下教程适用于所有异步,只是给小程序做了一下封装)。原理:增加事件队列,采用事件回调来完成同步化 以下代码复制粘贴到控制台即可测试效果;这里直接写es6代码了,先写个定时器版本的方便测试与理解先写个无注释版本的,方便直接看代码 class Async{ constructor() { this.list = []; this.sock = false; } request(obj) { setTimeout(() => { console.log(obj); this.sock = false; if(this.list[0]) this.do(this.list.shift()); }, 1000) } do(requestObj, async) { if(!async) { return this.request(requestObj); } if(this.sock) { this.list.push(requestObj); }else { this.sock = true; this.request(requestObj); } } }-----------以下为注释版本----------- class Async{ constructor() { this.list = []; // 定义 执行队列 this.sock = false; // 判断是否有任务正在执行 } request(obj) { setTimeout(() => { console.log(obj); this.sock = false; // 重置为没有任务正在执行 if(this.list[0]) // 如果队列中还有任务,执行下一个任务 this.do(this.list.shift()); }, 1000) // 模拟一个异步,一秒后执行任务,执行完成后执行下一个异步任务 } do(requestObj) { if(this.sock) // 如果有任务在执行 this.list.push(requestObj); // 将当前任务其增加到任务队列 else { this.sock = true; // 否则开始执行当前任务并设定'有任务在执行' this.request(requestObj); } } } var x = new Async(); x.do({url: 1}); // 一秒后打印 url: 1 x.do({url: 2}); // 两秒后打印 url: 2但是同步只是异步无可奈何的选择,所以不能全部否决掉异步 ...

July 14, 2019 · 2 min · jiezi

Lua-Web快速开发指南9-使用cf内置的异步库

API 介绍cf框架提供内置的异步库cf, 需要使用的时候我们必须先导入API: local cf = require "cf". 定时器与循环定时器cf库内置了一些定时器方法, 这些方法为开发者提供了对时间事件的控制能力. cf.timeout、cf.at、cf.sleep. cf.sleep方法是一个阻塞的定时器, 只有一个参数用来设置当前协程的休眠时间并且没有返回值. 此方法的行为(语义)取决于用户传入的参数: 当时间参数大于0的时候, 当前协程会暂停指定的时间且让出执行权. 当指定的时间超时后函数将会返回继续执行下面的代码.当时间参数等于0的时候, 当前协程会暂停并且让出执行权. 当其它协程执行完毕(让出)后立刻返回.当时间参数小于0或者非number类型的时候, 此方法将立刻返回.cf.timeout与cf.at不会阻塞当前协程执行流程. 目前虽然暴露给开发者使用, 但真正的使用场景都仅限于在需要长连接业务内. cf.timeout与cf.at都会返回一个timer对象, 开发者可以在任何时候使用timer对象的stop方法停止定时器. cf.timeout与cf.at的参数如下: 第一个参数是一个指定的时间, 其在现实中的时间比例为1:1.第二个参数是一个回调函数, 当时间事件触发后将会为用户执行用户定义的回调函数.记住: cf.timeout是一次性定时器, 回调函数被触发之后将会自动停止运行. 而cf.at如果不使用stop方法停止则会一直重复执行. 协程的使用、暂停、唤醒cf库提供了协程的操作方法. 此协程与Lua的原生协程有些许不同, cf基于原生协程的基础上由框架管理生命周期. 需要异步执行一个函数可以使用cf.fork创建一个由cf调度的协程, 此方法会返回一个协程对象. 这个协程对象可以在它让出的时候用来主动唤醒. cf.fork方法的第一个参数func为function类型, 从第二个参数开始的参数将会作为func的参数(一般情况下我们会利用upvalue而不会显示传递参数). 需要暂停一个cf创建的协程可以使用cf.wait方法. 此方法没有参数, 但如果调用此方法的协程不是由cf创建或不是main协程则会出错. cf.wakeup方法用于唤醒由cf.wait暂停的协程. cf.wait方法的返回值由cf.wakeup的行为决定, 当唤醒的是不存在的协程或唤醒正在执行的协程将会出错. cf.wakeup方法的第一个参数是一个协程对象, 协程对象之后的所有参数将会返回给cf.wait进行接收. 需要获取当前协程对象的时候在这个协程执行流程之间使用cf.self方法获取, cf.self的作用与内置库coroutine.running方法相同. 它返回一个协程对象与一个boolean值. 当协程对象为主(main)协程时则bolean为true, 否则为false. 更多详细的API介绍更多使用介绍请参考cf库的文档. 开始实践1. 随机生成三个定时器并且输出时间.在本示例中! 我们首先修改随机数生成器种子, 随机从0~1中间取一个随机数作为定时器的时间. 然后启动一个循环开始生成3个定时器. -- main.lualocal cf = require "cf"math.randomseed(os.time()) -- 设置随机数种子for index = 1, 3 do local time = math.random() cf.timeout(time, function() print("第"..index.."个定时器的时间为:"..time) end)end由于是随机生成的时间, 所以我们在函数内部使用print方法将当前定时器的运行信息打印出来(第几个创建的定时器与定时器时间). ...

June 24, 2019 · 2 min · jiezi

分布式消息队列详解10min搞懂同步和异步架构等问题

分布式消息队列是是大型分布式系统不可缺少的中间件,主要解决应用耦合、异步消息、流量削锋等问题。实现高性能、高可用、可伸缩和最终一致性架构。 对于一个架构师来说,在大型系统设计中,会经常需要面对同步和异步等架构问题,搞明白这些问题,能更好地实现程序并行执行,减少等待或无效操作,以及充分利用计算机的性能! 本文将详细讲解:1.同步架构和异步架构的区别 2.异步架构的主要组成部分:消息生产者、消息消费者、分布式消息队列 3.异步架构的两种主要模型:点对点模型和发布订阅模型。 4.消息队列的好处 5.消息队列相关产品 建议用10min通读,搞懂分布式消息队列的核心内容。 一、同步架构和异步架构的区别 1.同步调用 是指从请求的发起一直到最终的处理完成期间,请求的调用方一直在同步阻塞等待调用的处理完成。 如图,在这个例子中客户端代码ClientCode,需要执行发送邮件sendEmail这样一个操作,它会调用EmailService进行发送,而EmailService会调用SmtpEmailAdapter这样一个类来进行处理,而这个类会调用远程的一个服务,通过SMTP和TCP协议把请求发送给它。 而远程服务器收到消息以后会对消息进行一系列的操作,然后将邮件发送出去,再进行返回。Adapter收到返回后,再返回给EmailService,EmailService收到返回后再把返回结果返回给Clientcode。 ClientCode在sendEmail发出请求后,就一直都阻塞在这里,等待最终调用结果的返回,是成功还是失败。因为这个过程是阻塞等待的,所以这个过程也就是同步调用。 2.异步调用 是指在请求发起的处理过程中,客户端的代码已经返回了,它可以继续进行自己的后续操作,而不需要等待调用处理完成,这就叫做异步调用。 异步调用过程,同样看刚刚发送邮件的例子,用户Clientcode调用EmailService以后,EmailService会把这个调用请求发送给消息队列,然后就立即返回了。Clientcode收到返回以后继续向下处理,不会继续阻塞等待。实际上消息发送到Queue后,还没有被处理,我们看到后面的消息消费,其实要比EmailService返回可能还要晚一点,EmailService返回以后消息才会被消费处理。 有一个QueueConsumer消息队列的消费者,从消息队列中取出这个消息,再把这个消息发送给SmtpAdapter,也就是调用SmtpAdapter,处理逻辑跟同步调用一样,SmtpAdapter通过SMTP的通讯协议,把消息发送给远程的一个服务器,进行邮件发送,通过RemoteServer进行处理,处理完了收到返回,再把返回结果通知消息队列Queue。 在这个过程中,客户端的调用,也就是应用程序的调用,和业务逻辑真正发送邮件的操作是不同步的。 二、异步架构的主要组成部分 使用异步调用架构的主要手段,就是通过消息队列构建,如下是它的架构图。 消息的生产者将消息发送到消息队列以后,由消息的消费者从消息队列中获取消息,然后进行业务逻辑的处理,消息的生产者和消费者是异步处理的,彼此不会等待阻塞,所以叫做异步架构。 使用消息队列构建一个异步调用架构,你需要了解如下3种角色。 1.消息的生产者 是客户端应用程序代码的一部分,用来初始化异步调用处理流程。在基于消息队列的处理中,生产者的职责非常少,它要做的就是创建一个合法的消息,并把这个消息发送到消息队列中,由应用开发者决定生产者的代码在哪里执行,什么时候发送消息。 2.消息队列 消息队列是消息发送的目的地和发给消费者的一个缓冲。消息队列实现的方法有好多种,可以用共享文件夹,也可以用关系数据库或者NoSQL系统,当然最主要的还是使用专门的分布式消息队列服务器来实现。 3.消息的消费者 消息的消费者从消息队列中接受并处理消息,消息的消费者也是由应用开发者实现的,但是它是一个异步处理的组件。消息的消费者不需要知道生产者存在,它只依赖消息队列中的消息。消息的消费者通常部署在独立的服务器上,和消息的生产者完全隔离,并且可以通过添加硬件的方式进行伸缩。 三、异步架构的两种主要模型 使用消息队列构建异步的调用架构,你还需要知道两种模型:点对点模型和发布订阅模型。 1.点对点模型 消费者和生产者只需要知道消息队列的名字,生产者发送消息到消息队列中,而消息队列的另一端是多个消费者竞争消费消息,每个到达消息队列的消息只会被路由到一个消费者中去,所以消费者看到的是全部消息的一个子集。我们看这张图,消息的生产者有多个,消息的消费者也有多个,多个生产者将消息发送到消息队列中,而有多个消费者去消息队列中对消息进行竞争性的消费。每个消息只会被一个消费者消费,每个消费者只会消费消息队列中的一部分消息。 2.发布订阅模型 在发布订阅模型中,消息可能被发送到不止一个消费者,生产者发送消息到一个主题,而不是队列中。消息被发布到主题后,就会被克隆给每一个订阅它的消费者,每个消费者接收一份消息复制到自己的私有队列。消费者可以独立于其他消费者使用自己订阅的消息,消费者之间不会竞争消息。常用的分布式消息队列都支持发布订阅模型,也就是说消息的发布订阅模型是分布式消息队列的一个功能特性。 3.两个模型的应用 点对点模型:主要用于一些耗时较长的、逻辑相对独立的业务。 比如说我前面的讲到的发送邮件这样一个操作。因为发送邮件比较耗时,而且应用程序其实也并不太关心邮件发送是否成功,发送邮件的逻辑也相对比较独立,所以它只需要把邮件消息丢到消息队列中就可以返回了,而消费者也不需要关心是哪个生产者去发送的邮件,它只需要把邮件消息内容取出来以后进行消费,通过远程服务器将邮件发送出去就可以了。而且每个邮件只需要被发送一次。所以消息只被一个消费者消费就可以了。 发布订阅模型:如新用户注册这样一个消息,需要使用按主题发布的方式。 比如新用户注册,一个新用户注册成功以后,需要给用户发送一封激活邮件,发送一条欢迎短信,还需要将用户注册数据写入数据库,甚至需要将新用户信息发送给关联企业的系统,比如淘宝新用户信息发送给支付宝,这样允许用户可以一次注册就能登录使用多个关联产品。一个新用户注册,会把注册消息发送给一个主题,多种消费者可以订阅这个主题。比如发送邮件的消费者、发送短信的消费者、将注册信息写入数据库的消费者,跨系统同步消息的消费者等。 四、消息队列的好处 1.实现异步处理,提升处理性能 对一些比较耗时的操作,可以把处理过程通过消息队列进行异步处理。这样做可以推迟耗时操作的处理,使耗时操作异步化,而不必阻塞客户端的程序,客户端的程序在得到处理结果之前就可以继续执行,从而提高客户端程序的处理性能。 2.可以让系统获得更好的伸缩性 耗时的任务可以通过分布式消息队列,向多台消费者服务器并行发送消息,然后在很多台消费者服务器上并行处理消息,也就是说可以在多台物理服务器上运行消费者。那么当负载上升的时候,可以很容易地添加更多的机器成为消费者。 如图中的例子,用户上传文件后,通过发布消息的方式,通知后端的消费者获取数据、读取文件,进行异步的文件处理操作。那么当前端发布更多文件的时候,或者处理逻辑比较复杂的时候,就可以通过添加后端的消费者服务器,提供更强大的处理能力。 3.可以平衡流量峰值,削峰填谷 使用消息队列,即便是访问流量持续的增长,系统依然可以持续地接收请求。这种情况下,虽然生产者发布消息的速度比消费者消费消息的速度快,但是可以持续的将消息纳入到消息队列中,用消息队列作为消息的缓冲,因此短时间内,发布者不会受到消费处理能力的影响。 从这张图可以看到,因为消息的生产者是直接面向用户请求的,而用户的请求访问压力是不均衡的。如淘宝每天的访问高峰是在上午10点左右,而新浪微博则可能在某个明星半夜发一条微博后突然出现访问高峰。 在访问高峰,用户的并发访问数可能超过了系统的处理能力,所以在高峰期就可能会导致系统负载过大,响应速度变慢,更严重的可能会导致系统崩溃。这种情况下,通过消息队列将用户请求的消息纳入到消息队列中,通过消息队列缓冲消费者处理消息的速度。 如图中所示,消息的生产者它有高峰有低谷,但是到了消费者这里,只会按照自己的最佳处理能力去消费消息。高峰期它会把消息缓冲在消息队列中,而在低谷期它也还是使用自己最大的处理能力去获取消息,将前面缓冲起来、来不及及时处理的消息处理掉。那么,通过这种手段可以实现系统负载消峰填谷,也就是说将访问的高峰消掉,而将访问的低谷填平,使系统处在一个最佳的处理状态之下,不会对系统的负载产生太大的冲击。 4.失败隔离和自我修复 因为发布者不直接依赖消费者,所以分布式消息队列可以将消费者系统产生的错误异常与生产者系统隔离开来,生产者不受消费者失败的影响。 当在消息消费过程中出现处理逻辑失败的时候,这个错误只会影响到消费者自身,而不会传递给消息的生产者,也就是应用程序可以按照原来的处理逻辑继续执行。 所以,这也就意味着在任何时候都可以对后端的服务器执行维护和发布操作。可以重启、添加或删除服务器,而不影响生产者的可用性,这样简化了部署和服务器管理的难度。 5.可以使生产者和消费者的代码实现解耦合 也就是说可以多个生产者发布消息,多个消费者处理消息,共同完成完整的业务处理逻辑,但是它们的不需要直接的交互调用,没有代码的依赖耦合。在传统的同步调用中,调用者代码必须要依赖被调用者的代码,也就是生产者代码必须要依赖消费者的处理逻辑代码,代码需要直接的耦合,而使用消息队列,这两部分的代码不需要进行任何的耦合。 耦合程度越低的代码越容易维护,也越容易进行扩展。 比如新用户注册,如果用传统同步调用的方式,那么发邮件、发短信、写数据库、通知关联系统这些代码会和用户注册代码直接耦合起来,整个代码看起来就是完成用户注册逻辑后,后面必然跟着发邮件、发短信这些代码。如果要新增一个功能,比如将监控用户注册情况,将注册信息发送到业务监控系统,就必须要修改前面的代码,至少增加一行代码,发送注册信息到监控系统,我们知道,任何代码的修改都可能会引起bug。 而使用分布式消息队列实现生产者和消费者解耦合以后,用户注册以后,不需要调用任何后续处理代码,只需要将注册消息发送到分布式消息队列就可以了。如果要增加新功能,只需要写个新功能的消费者程序,在分布式消息队列中,订阅用户注册主题就可以了,不需要修改原来任何一行代码。 这种解耦的特点对于团队的工作分工也很有帮助!从消息生产者的视角看,它只需要构建消息,将消息放入消息队列中,开发就完成了。而从消费者的开发视角看,它只需要从消息队列中获取消息,然后进行逻辑处理。它们彼此之间不进行任何耦合。消息的生产者不关心放入消息队列中下一步会发生什么,而消费者也不需要知道消息从哪里来。这两部分程序的开发者也可以不关心彼此的工作进展,他们开发的代码也不需要集成在一起,只要约定好消息格式,就可以各自开发了。 ...

June 6, 2019 · 1 min · jiezi

报错-SyntaxError-async-with-outside-async-function

Python使用aiohttp的时候报错 SyntaxError: 'async with' outside async function 百度了一圈没有找到答案,因为我是按照官网文档打的,报错了,头大,还以为是包被我改坏了 结果,回看以前的代码,发现是因为,这个async with xxx as xxx:这个结构必须放在async def xxx():这样子的函数里面才行。 上代码 async def main(): async with aiohttp.ClientSession() as session: async with session.get(url, timeout=5) as resp: print(await resp.text())完整代码(做了一个嵌套) import aiohttpimport asyncioimport timeurl = 'http://docs.aiohttp.org/en/stable/client_quickstart.html'async def getapge(session, url): async with session.get(url,timeout=5) as resp: print(await resp.text())async def main(): async with aiohttp.ClientSession() as session: await getapge(session, url)#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>start = time.time()loop = asyncio.get_event_loop()loop.run_until_complete(main())end = time.time()#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<print('spend time is {}'.format(end - start))OK,问题解决 ...

June 4, 2019 · 1 min · jiezi

从js来聊聊异步编程

文章的目的揭开go的 gorouter,c#的 async/await等 使用同步的写法写异步代码的神秘面纱 , 证明其本质就是一个语法糖 为什么使用js来讲异步编程因为js可以通过编程语言自己的语法特性,实现async/await语法 js异步最底层写法promiseconst promise = new Promise(function(resolve, reject) { xxxxx.异步IO操作((res)=>{ if(res成功){ resolve(res) }else{ reject(res) } })});promise出入的回调函数有一定的要求 resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数(处理返回的结果)。promise.then(function(value) { // success}, function(error) { // failure});引申-注意: promise对象在js中非常特殊,比如下面的例子const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000)})const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000)})p2 .then(result => console.log(result)) .catch(error => console.log(error))这个的结果是failt 因为 p2中resolve返回一个promise对象,这个操作将会导致p2的状态升级成p1的状态(标准)promise的then链式写法promise then方法将会返回一个promise,所以js支持链式异步 ...

May 31, 2019 · 3 min · jiezi

今日头条网红题-????-async-functions-和-promises哪个更快

题目如下 async function async1() { console.log('async1 start') await async2() console.log('async1 end')}async function async2() { console.log('async2')}console.log('script start')setTimeout(function() { console.log('setTimeout') }, 0) async1()new Promise(function(resolve) { console.log('promise1') resolve()}).then(function() { console.log('promise2')})console.log('script end')而v8和node10产出的结果有所不同。 v8运行结果???? node10运行结果???? 先说下async/await原理???? async 声明的函数,其返回值必定是 promise 对象,如果没有显式返回 promise 对象,也会用 Promise.resolve() 对结果进行包装,保证返回值为 promise 类型await 会先执行其右侧表达逻辑(从右向左执行),并让出主线程,跳出 async 函数,而去继续执行 async 函数外的同步代码如果 await 右侧表达逻辑是个 promise,让出主线程,继续执行 async 函数外的同步代码,等待同步任务结束后,且该promise 被 resolve 时,继续执行 await 后面的逻辑如果 await 右侧表达逻辑不是 promise 类型,那么 async 函数之外的同步代码执行完毕之后,会回到 async函数内部,继续执行 await 之后的逻辑-- 摘自 LucasHC ...

May 29, 2019 · 1 min · jiezi

分布式系统关注点20阻塞与非阻塞有什么区别

如果第二次看到我的文章,欢迎「文末」扫码订阅我个人的公众号(跨界架构师)哟~ 每周五早8点 按时送达到公众号。当然了,也会时不时加个餐~前面一篇文章中,Z哥和你聊了「异步」的意义,以及如何运用它。错过这篇文章的可以先去看一下再来(分布式系统关注点——深入浅出「异步」)。 其实我知道有不少小伙伴容易将「异步」和「非阻塞」搞混。脑海里印象可能是这样的:异步=非阻塞,同步=阻塞? 其实并不是如此,Z哥我这次就想来帮你搞清楚这个问题。 同步与阻塞/非阻塞你平时编写的代码中,大部分的「同步」调用,本质上都是「阻塞」的。但是「同步」调用也可以做到「非阻塞」的效果。 还是拿我们上一篇中提到的排队买奶茶这个例子,看看为什么说是「同步」+「阻塞」。 文章里「同步」的例子说的是,你排队买奶茶,点完单继续“占着坑”,不让后面的人点单,等里面的店员做好奶茶,你拿走了后面的才能点单。这个其实就是「同步」+「阻塞」,「阻塞」体现在哪? 因为这个时候你一直“占着坑”,生怕后面的人先点单,导致店员给他先做。所以,这个时候你就死死的盯着里面,这个就是「阻塞」,因为你除了盯着其它啥都干不了。 怎么让「同步」也能不阻塞呢? 就是你虽然还是排着队“占着坑”,但是人没闲着,低头玩玩手机,时不时的问里面“我的奶茶做好了没?我的奶茶做好了没?”。这个就是「非阻塞」,因为你两次询问之间会间隔一段时间,可以在这个时候做其它的事情。本质上是通过将原本的一个「大同步」拆成多个「小同步」达到「非阻塞」的效果。 上图中,几次阻塞之间空白区域就可以用于做其它事,所以是「非阻塞」的。 异步与阻塞/非阻塞上一篇文章中的「异步」例子就是一个「非阻塞」的例子,我们来看看为什么。 奶茶店分了点单区和取餐区之后,做好的饮料就只能从取餐区拿,也意味着接待你进行点单的人并不是实际做奶茶的人。这个时候你会拿到一张取餐号,然后老老实实的去取餐区等着,而不是“占着xx不xx”。 如果你很着急要拿到奶茶,不断的问里面“我的奶茶做好了没?我的奶茶做好了没?”,那这个还是「同步」+「非阻塞」的模式。因为这个过程没有产生「回调」,是你在不断的主动发起“请求”。 但如果你不着急,就在边上开一局吃鸡,等着里面做好了叫号,到你号码了再去拿。这就是「异步」+「非阻塞」。因为这个事情是对方(里面的店员)触发完成的,这就是「回调」,是对你之前的“点单”请求进行的响应。一来一回完成一个完整的交互。 到这可能你会说,那异步不还是天然「非阻塞」的么?No、No、No。 阻塞不阻塞是你自己决定的,你可以阻塞啊。比如,你等的“回调”时候发现没带手机,玩不了吃鸡,那只能傻傻的在那等着,啥也干不了。如此,这个过程虽然还是「异步」的,但对你来说就是「阻塞」的。 工作中的同步/异步&阻塞/非阻塞「同步」+「阻塞」。这种最常见,平时写的大部分代码都是如此,就不多说了。 其实你仔细想一下就会发现,很多知名的框架,都是「同步」+「非阻塞」的,为什么呢?因为你可以继续像「同步」一样编写代码,但是可以享受到类似「异步」所能带来的更好的性能,何乐而不为? 比如大名鼎鼎的linux中的io复用模型poll/select/epoll,本质上都是「同步」+「非阻塞」的。还有知名网络通信框架Netty。 我们在设计对外的api的时候也可以使用这种模式,降低一些耗时接口调用所产生的影响。这个阮一峰老师已经写的非常清楚了,我就直接贴个链接:http://www.ruanyifeng.com/blo...。 之所以大家会有错觉,认为「异步」=「非阻塞」,其实也不是没有道理。为什么呢?因为我在脑海中搜寻来一番,的确没想到有什么知名的框架/设计是使用「异步」+「阻塞」来实现的。如果哪位小伙伴有补充,可以在评论区留言告诉大家。 「异步」+「非阻塞」就多了。任何你看到callback关键字的框架都是。 总结好了,我们一起总结一下。 这次呢,Z哥先通过同步/异步、阻塞/非阻塞之间形成的4种组合形式,聊了下它们到底是怎么回事。 然后和你聊了一下工作中哪里能看到它们的存在,以及在一些典型场景下适合用哪一种模式。 希望对你有所启发。 最后送你一个记住这4个概念的最好办法。 同步阻塞:你干吧,我看着你干同步非阻塞:你干吧,我每隔5分钟来看看异步阻塞:你干吧,好了告诉我,我等着异步非阻塞:你干吧,好了告诉我,我先去忙别的了如果还是记不住,那就记住同步/异步表示“过程”,阻塞/非阻塞表示在这个过程中的“状态”。至于这句话是怎么来的,回来看这篇文章就行。 相关文章: 分布式系统关注点——深入浅出「异步」分布式系统关注点——360°全方位解读「缓存」 作者:Zachary 出处:https://www.cnblogs.com/Zacha... 如果你喜欢这篇文章,可以点一下文末的「赞」。 这样可以给我一点反馈。: ) 谢谢你的举手之劳。 ▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码~。定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。 如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。

May 24, 2019 · 1 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

前端异步解决方案-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

前端异步解决方案-3(Promise)

又有好些天没有动笔了,这几天一直在断断续续的学习Promise和generator,今天终于能够把着两个玩意结合起来了解决异步问题了。今天我先把promise相关的用法和对异步的处理分享给大家。老样子,还是先模拟一个Promise。//咳咳这样就实现了嘛let MyPromise = Promise;开个玩笑,其实这两天我也一直在看Promise的实现,但是还是没有怎么理解。所以Promise的代码实现我暂时先放一放,等我完全理解再来新开一篇分享。这里先给大家推荐几篇我觉得比较好的Promise实现的博客,想要学习的小伙伴可以先到那边一睹为快,当然等我更新了之后大家还是要来给我文章点赞的哈。彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)这一篇文章用的es5的语法,对es6不是很熟悉的同学可以用这篇文章顶一下Promise实现原理(附源码)这一篇呢是用到了class的,所以需要大家对es6有所了解,也是一篇好文章接下来我介绍一下Promise最常用的几个部分:首先先介绍最简单的只有一个Promise对象的用法//Promise()接收一个函数,并且在创建这个Promise对象的同时,接收的这个函数就会被立刻执行var promise = new Promise(function (resolve, reject) { //resolve用来接收成功的返回,reject用来接收失败的返回 setTimeout(function () { //这里我们生成一个随机数,并在接下来根据这个随机数的大小来判断这个异步是否成功 let num = Math.random(); if (num > 0.8) { //接收失败的原因 console.log(“reject”) reject(num + “大于0.8,这次异步操作失败了”) } else { //接收成功的数据 console.log(“resolve”) resolve(num + “小于0.8,这次异步操作成功了”) } }, 100)});console.log(“一个Promise对象生成了”);//Promise对象的.then()方法接收两个回调,第一个为成功回调,第二个为失败回调//这两个回调会在上面的resolve或者reject函数生效后被调用promise.then(function (data) { //这个函数会在上面的resolve函数被调用后生效,这里的data就是上面resolve()中接收的值 console.log(data)}, function (err) { ///这个函数会在上面的reject函数被调用后生效,这里的err就是上面reject()中接收的值 console.error(err)});console.log(“promise的.then方法被调用了”)大家可以按F12唤起控制台,然后把这段代码运行几次。看看会有什么结果; 多运行几次以后,大家应该可以看到这两类结果: 其中的undefind是console.log(“promise的.then方法被调用了”)这行代码的返回,大家可以不用关注。在这里可以看到不论是成功的结果还是失败的结果都是在定时器执行后再打印出来的。这种写法可以帮助我们实现简单的异步编程。接下介绍多个Promise对象同时使用的用法, 先介绍最常见的.then()链式调用的方法 //用来快速生成一个Promise对象,接收一个日志列表,不论成功还是失败都会往日志列表中添加一条日志 function promise(log) { return new Promise(function (resolve, reject) { setTimeout(function () { log = log || []; //和上次的例子一样,利用随机数来随机失败和成功 let num = Math.random(); if (num > 0.5) { log.push(num + “大于0.5,这次异步操作失败了”); reject(log) } else { log.push(num + “小于0.5,这次异步操作成功了”); resolve(log) } }, 100) }) }.then()中返回了Promise对象的情况var promise1 = promise();//promise1.then()方法会返回一个Promise对象//如果我们在.then()生效的那个 !!!回调方法!!! 中有返回一个Promise对象的话,该对象会被 !!!.then()方法!!! 返回//先看返回了Promise对象的方式promise1.then(function (data) { console.log(data); return promise(data)}, function (err) { console.error(err); return promise(err)}).then(function (data) { console.log(data)}, function (err) { console.error(err)});这段代码运行后一共会有四种结果: 两次都成功 两次都失败 第一次失败,第二次成功 第一次成功,第二次失败通过这种方法我们可以用比较清晰的方式来书写我们的异步代码。特别是多个异步操作嵌套的时候,可以链式调用.then()来实现,这样的代码看起来逻辑更清晰; 刚刚看完了返回了Promise对象的场景,再来看看没有返回Promise的场景//如果我们没有返回Promise对象,.then()就会将我们返回的东西包装成一个Promise对象(没有返回就相当于返回了undefined)//可以等同于我们写了 return new Promise((resolve,reject)=>{resolve(/原先的返回值/)})promise1.then(function (data) { console.log(data); return data;}, function (err) { console.error(err); return err;}).then(function (data) { console.log(data)}, function (err) { //这里是永远不会被触发的,原因是上一个.then() 返回的是new Promise((resolve,reject)=>{resolve(/原先的返回值/)}) //返回的Promise对象的reject方法永远都不会被触发,所以这个里也就永远都不会触发了 console.error(err)});讲解都写在注释里面了,接下里我就贴运行图吧,这段代码会运行出以下两类结果:需要所有的请求都返回后才可以执行某个动作//改造promise,让其可以接收一个定时器等待时间参数function promise(log, time) { return new Promise(function (resolve, reject) { setTimeout(function () { log = log || []; //和上次的例子一样,利用随机数来随机失败和成功 let num = Math.random(); if (num > 0.5) { log.push(“等待时长” + time + “,” + num + “大于0.5,这次异步操作失败了”); console.error(log); reject(log) } else { log.push(“等待时长” + time + “,” + num + “小于0.5,这次异步操作成功了”); console.log(log); resolve(log) } }, time) })}//Promise.all()可以接收一个Promise对象的数组,返回一个Promise对象//该Promise对象会在数组中所有Promise成功返回后执行成功回调,在任意一个Promise失败后立刻执行失败回调var promise1 = promise(null, 10), promise2 = promise(null, 100), promise3 = Promise.all([promise1, promise2]);promise3.then((data) => { //这里的data为promise1,和promise2的返回值的数组 console.log(“promise3”, data)}, (err, err2) => { //报错信息 console.error(“promise3”, err)});这段代码一共有四种可能的结果 如果两次都成功的话 如果两次都成的话,promise3会执行成功回调,并且回调中的data就是promise1和promise2返回值的数组(数组顺序和.all()中的顺序一致)任意一个promise失败的话,promise3会立刻执行失败回调,并且回调中的err就是失败的那个promise在reject中返回的值文章写到这里,我认为Promise常用的一些用法都已经讲完了,更详细的Promise的教程请参考 MDN中对promise的讲解 ...

April 16, 2019 · 2 min · jiezi

前端异步解决方案-2(发布/订阅模式);

什么是发布订阅模式什么是发布订阅模式我这里不多就不多阐述了,给大家提供几个我觉得讲的比较好的博文,请各位自行阅读发布-订阅模式解释 这一篇文章应该是一个java coder写的,但是设计模式这种东西并不分语言,各位可以借鉴一下Javascript中理解发布–订阅模式这一篇是我们前端人写的,但是比较长,大家有耐心可以看看发布中心实现我对发布中心的实现,可以不看(看了能够更好的理解发布订阅模式)//实现发布中心/** log=[{* index: Number, 日志编号(自增)* type: String, 日志类型(‘subscribe’,‘unsubscribe’,‘publish’)* eventName: String, 事件名* time: new Date(), 时间* fun:Function 订阅/取订的方法(只有日志类型为’subscribe’或’unsubscribe’的才有)* param:Object 触发事件时的参数(只有日志类型为’publish’的才有)* }]* eventCenter = {* eventName:[Function,Function] //eventName即为事件名,其值为订阅方法列表* }* .subscribe(eventName, fun) 订阅 eventName:事件名 fun:订阅方法* .unsubscribe(eventName, fun) 取订 eventName:事件名 fun:订阅方法* .publish(eventName[,param]) 发布 eventName:事件名 param:事件参数* .showLog([filter]) 日志展示 filter 过滤器,同数组的过滤器用法 返回过滤后的log* .showEventCenter([eventName]) 事件中心 eventName 事件名 返回事件绑定的方法* /let subscribeCenter = function () { //事件中心 let eventCenter = {}; //日志 let log = []; //添加日志函数 function pushLog(type, eventName, fun, param) { let info = { index: log.length, type: type, eventName: eventName, time: new Date() }; if (fun) { info.fun = fun; } if (param) { info.param = param; } log.push(info) } return { //订阅 subscribe(eventName, fun) { pushLog(“subscribe”, eventName, fun); eventCenter[eventName] = eventCenter[eventName] || []; eventCenter[eventName].push(fun); }, //取消订阅 unsubscribe(eventName, fun) { pushLog(“unsubscribe”, eventName, fun); let onList = eventCenter[eventName]; if (onList) { for (let i = 0; i < onList.length; i++) { if (onList[i] === fun) { onList.splice(i, 1); return } } } }, //发布 publish(eventName, param) { pushLog(“publish”, eventName, null, param) let onList = eventCenter[eventName]; if (onList) { for (let i = 0; i < onList.length; i++) { onListi } } }, //显示日志 showLog(filter) { filter = filter || (() => true); let returnLog = log.filter(filter); returnLog.forEach(x => { let y = {}; for (let key in x) { y[key] = x[key] } return y }); return returnLog; }, //显示事件中心 showEventCenter(eventName) { let selectEM = eventName ? eventCenter[eventName] : eventCenter, returnEM = {}; for (let key in selectEM) { returnEM[key] = []; selectEM[key].forEach(x => { returnEM[key].push(x) }); } return returnEM } }}();如果有看我上一篇文章中事件监听实现的朋友应该对这个不部分代码有一种熟悉的感觉,确实事件监听和发布订阅的实现非常的像。我主要多做的就是一个日志的拓展,保证了每次的动作都可以被监听和查看;发布中心API这个在上面是有的,但是因为上面的部分有一些同学是不看的,所以就在这里摘出来,方便这些同学了解发布中心的用法/* log=[{* index: Number, 日志编号(自增)* type: String, 日志类型(‘subscribe’,‘unsubscribe’,‘publish’)* eventName: String, 事件名* time: new Date(), 时间* fun:Function 订阅/取订的方法(只有日志类型为’subscribe’或’unsubscribe’的才有)* param:Object 触发事件时的参数(只有日志类型为’publish’的才有)* }]* eventCenter = {* eventName:[Function,Function] //eventName即为事件名,其值为订阅方法列表* }* .subscribe(eventName, fun) 订阅 eventName:事件名 fun:订阅方法* .unsubscribe(eventName, fun) 取订 eventName:事件名 fun:订阅方法* .publish(eventName[,param]) 发布 eventName:事件名 param:事件参数* .showLog([filter]) 日志展示 filter 过滤器,同数组的过滤器用法 返回过滤后的log* .showEventCenter([eventName]) 事件中心 eventName 事件名 返回事件绑定的方法* */发布/订阅模式在异步中的应用这里是重点,需要大家仔细看,理解了这段代码不光可以解决异步问题,还可以理解发布订阅者模式是如何应用的;//发布者let f1 = function () { setTimeout(function () { console.log("‘done’ 事件发布 参数:", 123); subscribeCenter.publish(“done”, 123); console.log(“事件中心”, subscribeCenter.showEventCenter()); console.log(“f3 取订 ‘done’”); subscribeCenter.unsubscribe(“done”, f3); setTimeout(function () { console.log("‘done’ 事件发布 参数:", 233); subscribeCenter.publish(“done”, 233); console.log(“事件中心”, subscribeCenter.showEventCenter()); console.log(“日志”, subscribeCenter.showLog()); }, 100) }, 100)};//订阅者let f2 = function (param) { console.log(“f2 is running, param is”, param);};//订阅者let f3 = function (param) { console.log(“f3 is running, param is”, param)};//订阅console.log(“f2 订阅 ‘done’”);subscribeCenter.subscribe(“done”, f2);console.log(“f3 订阅 ‘done’”);subscribeCenter.subscribe(“done”, f3);//发布f1();先贴运行结果在这里可以看到,该模式与事件监听模式非常相似,但是所有的发布都通过了同一个发布中心来控制,这样的话可以方便我们追踪整个事件的状态; ...

April 5, 2019 · 2 min · jiezi

前端异步解决方案-1(callback,事件监听);

今天在学习koa2的时候发现自己对async的使用方法和原理都不是很熟悉,连带着发现自己对前端异步的解决方案并了解并不如何深刻,所以干脆把前端现有的异步解决方案都复习了一遍。今天先把将用callback和事件监听思想解决异步问题的代码贴出来,明天再补充代码的解析及其他解决方案;CallBackfunction f1(f2) { setTimeout(function () { let b = 10; f2(b) }, 1000)}function f2(data) { console.log(“callback_type_demo”, data)}f1(f2);事件监听//实现事件监听let DOM = function () { //被监听的事件的集合 this.eventList = {}; //绑定事件监听的方法 this.on = function (eventName, fun) { if (typeof eventName !== “string”) { console.error(“监听的事件名应为字符串”); return; } if (typeof fun !== “function”) { console.error(“被触发的必须是事件”); return; } this.eventList[eventName] = this.eventList[eventName] || []; this.eventList[eventName].push(fun); }; //移除事件监听的方法 this.removeOn = function (eventName, fun) { let onList = this.eventList[eventName]; if (onList) { for (let i = 0; i < onList.length; i++) { if (onList[i] === fun) { onList.splice(i, 1); return } } } }; //触发事件监听的方法 this.trigger = function (eventName, param) { let onList = this.eventList[eventName]; if (onList) { for (let i = 0; i < onList.length; i++) { onListi } } };};let dom1 = new DOM();let dom2 = new DOM();let f2 = function () { setTimeout(function () { dom1.trigger(‘done’, 20) }, 100)};let f3 = function (data) { console.log(data)};dom1.on(“done”, f3);f2();setTimeout(function () { console.log(“removeOn”); dom1.removeOn(“done”, f3); f2();}, 200);dom1.trigger(“done”, “123”);dom2.trigger(“done”, “123”);由于时间关系今天不多做解析,明天再来解析我的代码和运行结果 ...

April 4, 2019 · 1 min · jiezi

js异步从入门到放弃(二)- 传统的异步实现方案

前言上一篇文章介绍了js异步的底层基础–Event Loop模型,本文将介绍JS中传统的几种异步操作实现的模式。正文1.回调函数(callback)回调函数是异步的最基本实现方式。// 例子:回调函数const f1 = (callback) => setTimeout(()=>{ console.log(‘f1’) // 自身要执行的函数内容 callback()},1000)const f2 = () =>{ console.log(‘f2’) }f1(f2)思路:将回调函数作为参数传入主函数,执行完主函数内容之后,执行回调函数优点:简单粗暴、容易理解缺点:代码耦合度太高,不利于代码维护有多层回调的情况下,容易引起回调地狱一般回调的触发点只有一个,例如fs.readFile等函数,只提供传入一个回调函数,如果想触发2个回调函数,就只能再用一个函数把这两个函数包起来// 例子1:回调地狱,依次执行f1,f2,f3…const f1 = (callback) => setTimeout(()=>{ console.log(‘f1’) callback()},1000)const f2 = (callback) =>setTimeout(()=>{ console.log(‘f2’) callback()},1000)…// 假设还有f3,f4…fn都是类似的函数,那么就要不断的把每个函数写成类似的形式,然后使用下面的形式调用:f1(f2(f3(f4))) // 例子2:如果想给fs.readFile执行2个回调函数callback1,callback2// 必须先包起来const callback3 = ()=>{ callback1 callback2}fs.readFile(filename,[encoding],callback3)2.事件监听(Listener)事件监听的含义是:采用事件驱动模式,让任务的执行不取决于代码的顺序,而取决于某个事件是否发生。先给出实现的效果:const f1 = () => setTimeout(()=>{ console.log(‘f1’) // 函数体 f1.trigger(‘done’) // 执行完函数体部分 触发done事件},1000)f1.on(‘done’,f2) // 绑定done事件回调函数f1()// 一秒后输出 f1,再过一秒后输出f2接下来手动实现一下上面的例子,体会一下这种方案的原理:const f1 = () => setTimeout(()=>{ console.log(‘f1’) // 函数体 f1.trigger(‘done’) // 执行完函数体部分 触发done事件},1000)/—————-核心代码start——————————–/// listeners 用于存储f1函数各种各样的事件类型和对应的处理函数f1.listeners = {}// on方法用于绑定监听函数,type表示监听的事件类型,callback表示对应的处理函数f1.on = function (type,callback){ if(!this.listeners[type]){ this.listeners[type] = [] } this.listeners[type].push(callback) //用数组存放 因为一个事件可能绑定多个监听函数}// trigger方法用于触发监听函数 type表示监听的事件类型f1.trigger = function (type){ if(this.listeners&&this.listeners[type]){ // 依次执行绑定的函数 for(let i = 0;i < this.listeners[type].length;i++){ const fn = this.listeners[type][i] fn() } }}/—————-核心代码end——————————–/const f2 = () =>setTimeout(()=>{ console.log(‘f2’)},1000)const f3 = () =>{ console.log(‘f3’) }f1.on(‘done’,f2) // 绑定done事件回调函数f1.on(‘done’,f3) // 多个回调f1()// 一秒后输出 f1, f3,再一秒后输出f2核心原理:用listeners对象储存要监听的事件类型和对应的函数;调用on方法时,往listeners中对应的事件类型添加回调函数;调用trigger方法时,检查listeners中对应的事件,如果存在回调函数,则依次执行;和回调相比,代码上的区别只是把原先执行callback的地方,换成了执行对应监听事件的回调函数。但是从模式上看,变成了事件驱动模型。优点:避免了直接使用回调的高耦合问题,可以绑定多个回调函数缺点:由事件驱动,不容易看出执行的主流程3.发布/订阅模式(Publish/Subscribe)在刚刚事件监听的例子中,我们改造了f1,使它拥有了添加监听函数和触发事件的功能,如果我们把这部分功能移到另外一个全局对象上实现,就成了发布订阅者模式:// 消息中心对象const Message = { listeners:{}}// subscribe方法用于添加订阅者 类似事件监听中的on方法 里面的代码完全一致Message.subscribe = function (type,callback){ if(!this.listeners[type]){ this.listeners[type] = [] } this.listeners[type].push(callback) //用数组存放 因为一个事件可能绑定多个监听函数}// publish方法用于通知消息中心发布特定的消息 类似事件监听中的trigger 里面的代码完全一致Message.publish = function (type){ if(this.listeners&&this.listeners[type]){ // 依次执行绑定的函数 for(let i = 0;i < this.listeners[type].length;i++){ const fn = this.listeners[type][i] fn() } }}const f2 = () =>setTimeout(()=>{ console.log(‘f2’)},1000)const f3 = () => console.log(‘f3’)Message.subscribe(‘done’,f2) // f2函数 订阅了done信号Message.subscribe(‘done’,f3) // f3函数 订阅了done信号const f1 = () => setTimeout(()=>{ console.log(‘f1’) Message.publish(‘done’) // 消息中心发出done信号},1000)f1() // 执行结果和上面完全一样如果认真看的话会发现,这里的代码和上一个例子几乎没有区别,仅仅是:创建了一个Message全局对象,并且listeners移到该对象on方法改名为subscribe方法,并且移到Message对象上trigger方法改名为publish,并且移到Message对象上这么做有意义吗?当然有。在事件监听模式中,消息传递路线:被监听函数f1与监听函数f2直接交流在发布/订阅模式中,是发布者f1和消息中心交流,订阅者f2也和消息中心交流如图:消息中心的作用正如它的名字–承担了消息中转的功能,所有发布者和订阅器都只和它进行消息传递。有这个对象的存在,可以更方便的查看全局的消息订阅情况。实质上,这也是设计模式中,观察者模式和发布/订阅者模式的区别。4.PromisePromise 是异步编程的一种解决方案,它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。注意,只是在es6原生提供了Promise对象,不代表Promise的设计是在es6才出现的。最典型的,当我们还在使用jquery的$.ajax时,已经使用$.ajax().then().catch()时,就已经用到了Promise对象。因此这个也归为传统异步实现。关于Promise详细内容,建议大家学习阮一峰老师的ES6教程,本文只介绍异步相关的核心内容。接下来同样地,用js模拟实现一个简单的Promise对象。首先分析Promise的要点:构造函数接受一个函数为参数,并且要接受resolve(reject)方法可以通过resolve和reject方法改变状态:resolve使状态从pending(进行中)变成、fulfilled(已成功);reject使状态变成rejected(已失败)then方法用于注册回调函数,并且返回值必须为Promise对象,这样才能实现链式调用(链式调用是指p.then().then().then()这样的形式)根据上述分析,实现一个有then和resolve方法的简单Promise对象:// 例子:手动实现简单Promisefunction MyPromise(fn){ this.status = ‘pending’ this.resolves =[] //存放成功执行后的回调函数 return fn(this.resolve.bind(this))// 这里必须bind,否则this对象会根据执行上下文改变}// then方法用于添加注册回调函数MyPromise.prototype.then = function(fn){ // 注册回调函数 并返回Promise. this.resolves.push(fn) return this}// resolve用于变更状态 并且触发回调函数,实际上resolve可以接受参数 这里简单实现就先忽略MyPromise.prototype.resolve = function(){ this.status = ‘fulfilled’ if(this.resolves.length===0){ return } // 依次执行回调函数 并清空 for(i=0;i<this.resolves.length;i++){ const fn = this.resolves[i] fn() } this.resolves = [] //清空 return this}// 使用写好的MyPromise做实验const f1 = new MyPromise(resolve=>{ setTimeout(()=>{ console.log(‘f1 开始运行’) resolve() },1000)})f1.then(()=>{ setTimeout(()=>{ console.log(‘f1的第一个then’) },3000)})// 一个小思考,下面函数的执行输出是什么?f1.then(()=>{ setTimeout(()=>{ console.log(‘f1的第一个then’) },3000)}).then(()=>{ setTimeout(()=>{ console.log(‘f1的第二个then’) },1000)})以上就是Promise的核心思路。总结本文针对传统的几种异步实现方案做了说明。而ES6中新的异步处理方案Generator和async/await会在后面补充。如果觉得写得不好/有错误/表述不明确,都欢迎指出如果有帮助,欢迎点赞和收藏,转载请征得同意后著明出处。如果有问题也欢迎私信交流,主页有邮箱地址如果觉得作者很辛苦,也欢迎打赏一杯咖啡~ ...

March 18, 2019 · 2 min · jiezi

js异步从入门到放弃(一)- Event Loop模型

前言异步一直是前端开发里最让人头疼的一个难点,接下来的几篇文章,将围绕这个话题展开。1. 单线程的语言-JavaScript众所周知,JS最初的目的是用于处理浏览器的用户交互和操作DOM,因此,如果JS设计成允许同时存在2个以上的线程,就会出现以下这种问题:2个线程同时操作了同一个DOM节点(a线程要编辑该节点,而b线程删除该节点),那么此时浏览器将无法处理,因为无法判断以哪个线程为基准。因此,JS只能是单线程。(Web Worker API虽然提供了多线程,但只是纯粹基于使用多核cpu的计算能力,其创建的子线程严格受控,不影响JS单线程的设计实质),单线程的设计就意味着,任务以排队的方式依此执行。基于单线程设计,不可避免的遇到一个情形:某些任务需要的时间很长,但不是因为任务本身太过复杂,难以处理,而是输入输出太慢(例如Ajax获取数据)。而在等待输入输出的过程中,CPU是闲置的,为了充分利用资源,这一类任务被设计成允许暂时挂起,等到有了结果再执行的任务。现在有两种任务了:同步任务和异步任务接下来介绍JS的处理机制。2. Event Loop理论基础首先看来自MDN的一张图:栈(stack),函数调用堆栈。看这个例子: function a(){ console.log(‘a’) } function b(){ console.log(‘from’) a() // 这里调用了函数a } b()在Chrome中运行,并且单步调试,可以看到以下步骤:执行b()时,函数b进栈(如图1)在b中调用函数a时,a继续进栈(如图2)函数a执行完毕,出栈(如图1)(这部分内容实际上对应着之前介绍闭包时,函数作用域链的生成部分,传送门)堆(heap),内存区,用于存储对象。(这个目前不是很重要先不用管)队列(queue),待处理消息队列,每一个消息都关联着一个用以处理这个消息的函数。常见示例:让页面中的某个按钮,点击时触发handleClick函数,那么,当用户触发点击按钮的动作时,会有一个待处理消息进入queue,关联的函数为handleClick。发起一个ajax请求,当请求有结果之后,会有一个待处理消息进入queue,关联的函数为所指定的回调函数整体运行过程整体的执行过程如下(如图):主线程执行同步代码,执行过程会产生对应的函数调用栈stack,如果碰到有异步事件,如发起ajax请求,则提交给对应的异步模块处理,当异步任务有结果时,异步模块负责在消息队列中添加待处理的消息;当同步任务处理完成,函数调用栈清空时,主线程检查消息队列queue:如果消息队列不为空,那么从消息队列头部取出一个待处理的消息,进入主线程;主线程重复以上过程上述过程循环执行,所以称为事件循环(Event Loop)// 简单的例子 var req = new XMLHttpRequest(); req.open(‘GET’, url); req.onload = function (){}; //指定回调函数, 这是一个异步任务,会被先提交到异步处理的api,等有了结果才会添加到消息队列 req.send();*任务队列类型补充说明以下,任务队列分成2类:microtask queue:ES6 的 promise产生的任务队列macrotask queue:除microtask queue以外的任务产生的任务队列,如(事件触发 setTimeout Ajax请求)他们的区别下次讲解Promise时再说明(挖个坑)3.定时器上述Event Loop模型中,消息队列的新消息来源,除了有dom事件操作,ajax请求等,也可能是定时任务,也就是由setTimeout创建的任务。这个函数大家肯定不陌生,但是也可能未必真的足够熟悉~。setTimeout接受两个参数:回调函数延迟执行的毫秒数。(严格来说,应该是实际加入到主线程的最小延迟时间,为什么呢,往下看)现在看下以下2个例子://示例1console.log(1);setTimeout(function(){console.log(2);},1000);console.log(3);// 输出结果 1 3 2 ,因为setTimeout指定了里面的函数要推迟1000毫秒才会执行这个例子说明了setTimeout的基本作用,比较简单不多说。//示例2const s = new Date().getSeconds(); //获取当前的秒数setTimeout(function() { // 输出 “2”,表示回调函数并没有在 500 毫秒之后立即执行 console.log(“Ran after " + (new Date().getSeconds() - s) + " seconds”);}, 500);while(true) {//这个循环含义就是,至少要过2s,当前主线程任务才执行完毕 if(new Date().getSeconds() - s >= 2) { console.log(“Good, looped for 2 seconds”); break; }}//实际输出Good, looped for 2 secondseventloop.html:15 Ran after 2 seconds这个例子,首先使用setTimeout指定了一个500毫秒后执行的回调函数,然后使用while循环故意让当前运行超过2秒钟,根据上文的流程图可知:其实在第500毫秒时,这个消息已经被添加到消息队列,但是由于当前的主线程并没有执行完,调用栈尚未清空,所以在500毫秒不会执行setTimeout指定的回调函数。实际上,即使把上述代码中的500改成0,结果也是一样的。简而言之,setTimeout(fn,x毫秒)的x只是指定了fn被执行的最小等待时间,息具体能在多少时间之后执行,取决于现有调用栈函数的执行进度,以及消息队列中前面的任务执行进度。小结本文介绍了Event Loop模型过程以及常见的任务队列的几种任务队列消息来源,这是JS异步话题的基础篇。参考文献:MDN-EventLoopJavaScript 运行机制详解:再谈Event Loop惯例:如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请征得同意后著明出处,如果有问题也欢迎私信交流,主页有邮箱地址 ...

March 17, 2019 · 1 min · jiezi

循环中的异步&&循环中的闭包

原文链接在这之前先要了解一下for循环中let 和var的区别var 是函数级作用域或者全局作用域,let是块级作用域看一个例子 function foo() { for (var index = 0; index < array.length; index++) { //..循环中的逻辑代码 } console.log(index);//=>5 } foo() console.log(index)//Uncaught ReferenceError: index is not definedfoo函数下的index输出5,全局下的index不存在现在我们把var 换为let function foo() { for (let index = 0; index < array.length; index++) { //..循环中的逻辑代码 } console.log(index)//Uncaught ReferenceError: index is not defined } foo()报错了,index不在foo函数作用域下,当然肯定也不会再全局下因为var和let的这个区别(当然var和let的区别不止于此)所以导致了下面的这个问题关于var的 const array = [1, 2, 3, 4, 5] function foo() { for (var index = 0; index < array.length; index++) { setTimeout(() => { console.log(index); }, 1000); } } foo()看下输出关于let的 const array = [1, 2, 3, 4, 5] function foo() { for (let index = 0; index < array.length; index++) { setTimeout(() => { console.log(index); }, 1000); } } foo()看下输出因为var和let 在作用域上的差别,所以到这了上面的问题使用var 定义变量的时候,作用域是在foo函数下,在for循环外部,在整个循环中是全局的,每一次的循环实际上是为index赋值,循环一次赋值一次,5次循环完成,index最后的结果赋值就为5;就是被最终赋值的index,就是5;let的作用局的块级作用局,index的作用域在for循环内部,即每次循环的index的作用域就是本次循环,下一次循环重新定义变量index;所以index每次循环的输出都不同这里还有另外一个问题,setTimeout,这是一个异步,这就是我们今天要讨论的循环中的异步setTimeout(func,time)函数运行机制setTimeout(func,time)是在time(毫秒单位)时间后执行func函数。浏览器引擎按顺序执行程序,遇到setTimeout会将func函数放到执行队列中,等到主程序执行完毕之后,才开始从执行队列(队列中可能有多个待执行的func函数)中按照time延时时间的先后顺序取出来func并执行。即使time=0,也会等主程序运行完之后,才会执行。一个需求,一个数组array[1,2,3,4,5],循环打印,间隔1秒上面的let是循环打印了12345,但是不是间隔1s打印的,是在foo函数执行1s后,同时打印的方式一 放弃for循环,使用setInterval function foo(){ let index = 0; const array = [1, 2, 3, 4, 5] const t = setInterval(()=>{ if (index < array.length) { console.log(array[index]); } index++; }, 1000); if (index >= array.length) { clearInterval(t); } } foo()我们上面说到,当for循环遇到了var,变量index的作用域在foo函数下,循环一次赋值一次,5次循环完成,index最后的结果赋值就为5;就是被最终赋值的index,就是5;方式二,引入全局变量代码执行顺序是,先同步执行for循环,再执行异步队列,在for循环执行完毕后,异步队列开始执行之前,index经过for循环的处理,变成了5。所以我们引入一个全局变量j,使j在for循环执行完毕后,异步队列开始执行之前,依然是0,在异步执行时进行累加 var j = 0; for (var index = 0; index < array.length; index++) { setTimeout(() => { console.log(j); j++; }, 1000 * index) }方式三 for循环配合setTimeout(常规思路,不赘述,面试必备技能) const array = [1, 2, 3, 4, 5] function foo() { for (let index = 0; index < array.length; index++) { setTimeout(() => { console.log(index); }, 1000*index); } } foo()方式四,通过闭包实现开始讨论方式四之前我推荐先阅读一遍我之前写过一篇文章谈一谈javascript作用域我们对上面的问题再次分析,for循环同步执行,在for循环内部遇到了setTimeout,setTimeout是异步执行的,所以加入了异步队列,当同步的for循环执行完毕后,再去执行异步队列,setTimeout中有唯一的一个参数数index方式三可行,是因为let是块级作用域,每次for执行都会创建新的变量index,for循环执行完毕后,异步执行之前,创建了5个独立的作用域,5个index变量,分别是0,1,2,3,4,相互独立,互不影响,输出了预期的结果如果说每次循环都会生成一个独立的作用域用来保存index,问题就会得到解决,所以,我们通过闭包来实现 const array = [1, 2, 3, 4, 5] function foo() { for (var index = 0; index < array.length; index++) { function fun(j) { setTimeout(function () { console.log(j); }, 1000 * j); } fun(index) } } foo()setTimeout中的匿名回调函数中引用了函数fun中的局部变量j,所以当fun执行完毕后,变量j不会被释放,这就形成了闭包当然我们可以对此进行一下优化 const array = [1, 2, 3, 4, 5] function foo() { for (var index = 0; index < array.length; index++) { (function(j) { setTimeout(function () { console.log(j); }, 1000 * j); })(index) } } foo()将foo函数改为匿名的立即执行函数,结果是相同的总结for循环本身是同步执行的,当在for循环中遇到了异步逻辑,异步就会进入异步队列,当for循环执行结束后,才会执行异步队列当异步函数依赖于for循环中的索引时(一定是存在依赖关系的,不然不会再循环中调动异步函数)要考虑作用域的问题,在ES6中使用let是最佳的选择,当使用var时,可以考虑再引入一个索引来替代for循环中的索引,新的索引逻辑要在异步中处理也可以使用闭包,模拟实现let在实际开发过程中,循环调用异步函数,比demo要复杂,可能还会出现if和else判断等逻辑,具体的我们下次再续参考通过for循环每隔两秒按顺序打印出arr中的数字setTimeOut和闭包《你不知道了JavaScript》上卷 ...

March 4, 2019 · 2 min · jiezi

怎样针对JavaScript中的异步函数进行异常处理及测试

翻译:疯狂的技术宅原文:https://www.valentinog.com/bl…本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章可以在 Javascript 的异步函数中抛出错误吗?这个话题已被反复提起过几百次,不过这次让我们从TDD(Test-Driven Development)的角度来回答它。如果你能不在Stackoverflow上搜索就能回答这个问题,会给我留下深刻的印象。如果不能的话也可以很酷。 继续往下读,你就能学到!你将学到什么通过后面的内容你将学到:如何从 Javascript 的异步函数中抛出错误如何使用 Jest 测试来自异步函数的异常要求要继续往下读你应该:对 Javascript 和 ES6 有基本的了解安装 Node.Js 和 Jest如何从 Javascript 的常规函数中抛出错误使用异常而不是返回码(清洁代码)。抛出错误是处理未知的最佳方法。同样的规则适用于各种现代语言:Java、Javascript、Python、Ruby。你可以从函数中抛出错误,可以参照以下示例:function upperCase(name) { if (typeof name !== “string”) { throw TypeError(“name must be a string”); } return name.toUpperCase();}module.exports = upperCase;这是对它的测试(使用Jest):“use strict”;const assert = require(“assert”);const upperCase = require("../function");describe(“upperCase function”, () => { test(“it throws when name is not provided”, () => { assert.throws(() => upperCase()); }); test(“it throws when name is not a string”, () => { assert.throws(() => upperCase(9)); });});也可以从 ES6 的类中抛出错误。在 Javascript 中编写类时,我总是在构造函数中输入意外值。下面是一个例子:class Person { constructor(name) { if (typeof name !== “string”) { throw TypeError(“name must be a string”); } this.name = name; } // some method here}module.exports = Person;以下是该类的测试:“use strict”;const assert = require(“assert”);const Person = require("../index");describe(“Person class”, () => { test(“it throws when name is not provided”, () => { assert.throws(() => new Person()); }); test(“it throws when name is not a string”, () => { assert.throws(() => new Person(9)); });});测试确实通过了:PASS test/index.test.js Person class ✓ it throws when name is not provided (1ms) ✓ it throws when name is not a string安排的明明白白!所以无论异常是从常规函数还是从类构造函数(或从方法)抛出的,一切都会按照预期工作。但是如果我想从异步函数中抛出错误怎么办?我可以在测试中使用assert.throws吗?各位看官请上眼!测试异常既然都看到这里了,所以你应该知道什么是 Javascript 的异步函数,对吗?先看一段代码:class Person { constructor(name) { if (typeof name !== “string”) { throw TypeError(“name must be a string”); } this.name = name; } // some method here}module.exports = Person;假设你要添加异步方法来获取有关该人的数据。这种方法需要一个网址。如果url不是字符串,就要像上一个例子中那样抛出错误。先来修改一下这个类:class Person { constructor(name) { if (typeof name !== “string”) { throw TypeError(“name must be a string”); } this.name = name; } async getData(url) { if (typeof url !== “string”) { throw TypeError(“url must be a string”); } // const response = await fetch(url) // do stuff }}module.exports = Person;如果我运行代码会怎么样?试试吧:const Person = require("../index");const valentinogagliardi = new Person(“valentinogagliardi”);valentinogagliardi.getData();结果是这样UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: name must be a stringDeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.果然不出所料,异步方法返回了一个Promise rejection,从严格意义上来讲,并没有抛出什么东西。错误被包含在了Promise rejection中。换句话说,我不能使用 assert.throws 来测试它。让我们通过测试来验证一下:“use strict”;const assert = require(“assert”);const Person = require("../index");describe(“Person methods”, () => { test(“it throws when url is not a string”, () => { const valentinogagliardi = new Person(“valentinogagliardi”); assert.throws(() => valentinogagliardi.getData()); });});测试失败了!FAIL test/index.test.js Person methods › it throws when url is not a string assert.throws(function) Expected the function to throw an error. But it didn’t throw anything. Message: Missing expected exception.有没有悟出点什么?看把你能的,来抓我啊从严格意义上讲异步函数和异步方法不会抛出错误。异步函数和异步方法总是返回一个Promise,无论它已完成还是被拒绝,你必须附上 then() 和 catch(),无论如何。(或者将方法包装在try/catch中)。被拒绝的Promise将会在堆栈中传播,除非你抓住(catch)它。至于测试代码,应该这样写:“use strict”;const assert = require(“assert”);const Person = require("../index");describe(“Person methods”, () => { test(“it rejects when url is not a string”, async () => { expect.assertions(1); const valentinogagliardi = new Person(“valentinogagliardi”); await expect(valentinogagliardi.getData()).rejects.toEqual( TypeError(“url must be a string”) ); });});我们测试的不能是普通的异常,而是带有TypeError的rejects。现在测试通过了:PASS test/index.test.js Person methods ✓ it rejects when url is not a string那代码该怎么写呢?为了能够捕获错误,你应该这样重构:const Person = require("../index");const valentinogagliardi = new Person(“valentinogagliardi”);valentinogagliardi .getData() .then(res => res) .catch(err => console.error(err));现在异常将会出现在控制台中:TypeError: url must be a string at Person.getData (/home/valentino/Documenti/articles-and-broadcasts/throw-from-async-functions-2018-04-02/index.js:12:13) at Object.<anonymous> (/home/valentino/Documenti/articles-and-broadcasts/throw-from-async-functions-2018-04-02/index.js:22:4) // …如果你想要更多的try/catch.,有一件重要的事需要注意。下面的代码不会捕获错误:const Person = require("../index");async function whatever() { try { const valentinogagliardi = new Person(“valentinogagliardi”); await valentinogagliardi.getData(); // do stuff with the eventual result and return something } catch (error) { throw Error(error); }}whatever();记住:被拒绝的Promise会在堆栈中传播,除非你抓住(catch)它。要在 try/catch 中正确捕获错误,可以像这样重构:async function whatever() { try { const valentinogagliardi = new Person(“valentinogagliardi”); await valentinogagliardi.getData(); // do stuff with the eventual result and return something } catch (error) { throw Error(error); }}whatever().catch(err => console.error(err));这就是它的工作原理。总结最后总结一下:从异步函数抛出的错误不会是“普通的异常”。异步函数和异步方法总是返回一个Promise,无论是已解决还是被拒绝。要拦截异步函数中的异常,必须使用catch()。以下是在Jest中测试异常的规则:使用 assert.throws 来测试普通函数和方法中的异常使用 expect + rejects 来测试异步函数和异步方法中的异常如果你对如何使用 Jest 测试 Koa 2 感兴趣,请查看使用Jest和Supertest进行测试的简绍这篇文章。感谢阅读!本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章 ...

January 25, 2019 · 3 min · jiezi

JavaScript事件循环(Event Loop)

1、为什么要有事件循环?因为js是单线程的,事件循环是js的执行机制,也是js实现异步的一种方法。既然js是单线程,那就像只有一个窗口的银行,客户需要排队一个一个办理业务,同理js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:同步任务异步任务当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。2、宏任务与微任务JavaScript中除了广泛的同步任务和异步任务,我们对任务有更精细的定义:macro-task(宏任务): 包括整体代码script,setTimeout,setIntervalmicro-task(微任务): Promise,process.nextTick不同的类型的任务会进入不同的Event Queue(事件队列),比如setTimeout、setInterval会进入一个事件队列,而Promise会进入另一个事件队列。一次事件循环中有宏任务队列和微任务队列。事件循环的顺序,决定js代码执行的顺序。进入整体代码(宏任务-<script>包裹的代码可以理解为第一个宏任务),开始第一次循环,接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列的任务执行完毕,再执行所有的微任务。如:<script> setTimeout(function() { console.log(‘setTimeout’); }) new Promise(function(resolve) { console.log(‘promise’); }).then(function() { console.log(’then’); }) console.log(‘console’); /* —————————-分析 start——————————— / 1、&lt;script&gt;中的整段代码作为第一个宏任务,进入主线程。即开启第一次事件循环 2、遇到setTimeout,将其回调函数放入Event table中注册,然后分发到宏任务Event Queue中 3、接下来遇到new Promise、Promise,立即执行;将then函数分发到微任务Event Queue中。输出: promise 4、遇到console.log,立即执行。输出: console 5、整体代码作为第一个宏任务执行结束,此时去微任务队列中查看有哪些微任务,结果发现了then函数,然后将它推入主线程并执行。输出: then 6、第一轮事件循环结束 开启第二轮事件循环。先从宏任务开始,去宏任务事件队列中查看有哪些宏任务,在宏任务事件队列中找到了setTimeout对应的回调函数,立即执行之。此时宏任务事件队列中已经没有事件了,然后去微任务事件队列中查看是否有事件,结果没有。此时第二轮事件循环结束;输出:setTimeout / —————————-分析 end——————————— */</script>3、分析更复杂的代码<script> console.log(‘1’); setTimeout(function() { console.log(‘2’); process.nextTick(function() { console.log(‘3’); }) new Promise(function(resolve) { console.log(‘4’); resolve(); }).then(function() { console.log(‘5’) }) }) process.nextTick(function() { console.log(‘6’); }) new Promise(function(resolve) { console.log(‘7’); resolve(); }).then(function() { console.log(‘8’) }) setTimeout(function() { console.log(‘9’); process.nextTick(function() { console.log(‘10’); }) new Promise(function(resolve) { console.log(‘11’); resolve(); }).then(function() { console.log(‘12’) }) })</script>一、第一轮事件循环a)、整段<script>代码作为第一个宏任务进入主线程,即开启第一轮事件循环b)、遇到console.log,立即执行。输出:1c)、遇到setTimeout,将其回调函数放入Event table中注册,然后分发到宏任务事件队列中。我们将其标记为setTimeout1d)、遇到process.nextTick,其回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为process1e)、遇到new Promise、Promise,立即执行;then回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为then1。输出: 7f)、遇到setTimeout,将其回调函数放入Event table中注册,然后分发到宏任务事件队列中。我们将其标记为setTimeout2此时第一轮事件循环宏任务结束,下表是第一轮事件循环宏任务结束时各Event Queue的情况-宏任务事件队列微任务事件队列第一轮事件循环(宏任务已结束)process1、then1第二轮事件循环(未开始)setTimeout1 第三轮事件循环(未开始)setTimeout2 可以看到第一轮事件循环宏任务结束后微任务事件队列中还有两个事件待执行,因此这两个事件会被推入主线程,然后执行g)、执行process1。输出:6h)、执行then1。输出:8第一轮事件循环正式结束!二、第二轮事件循环a)、第二轮事件循环从宏任务setTimeout1开始。遇到console.log,立即执行。输出: 2b)、遇到process.nextTick,其回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为process2c)、遇到new Promise,立即执行;then回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为then2。输出: 5此时第二轮事件循环宏任务结束,下表是第二轮事件循环宏任务结束时各Event Queue的情况-宏任务事件队列微任务事件队列第一轮事件循环(已结束) 第二轮事件循环(宏任务已结束)process2、then2第三轮事件循环(未开始)setTimeout2 可以看到第二轮事件循环宏任务结束后微任务事件队列中还有两个事件待执行,因此这两个事件会被推入主线程,然后执行d)、执行process2。输出:3e)、执行then2。输出:5第二轮事件循环正式结束!三、第三轮事件循环a)、第三轮事件循环从宏任务setTimeout2开始。遇到console.log,立即执行。输出: 9d)、遇到process.nextTick,其回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为process3c)、遇到new Promise,立即执行;then回调函数放入Event table中注册,然后被分发到微任务事件队列中。记为then3。输出: 11此时第三轮事件循环宏任务结束,下表是第三轮事件循环宏任务结束时各Event Queue的情况-宏任务事件队列微任务事件队列第一轮事件循环(已结束) 第二轮事件循环(已结束) 第三轮事件循环(未开始)(宏任务已结束)process3、then3可以看到第二轮事件循环宏任务结束后微任务事件队列中还有两个事件待执行,因此这两个事件会被推入主线程,然后执行d)、执行process3。输出:10e)、执行then3。输出:124、参考文章https://juejin.im/post/59e85e… ...

January 21, 2019 · 1 min · jiezi

Swoole 2019 :化繁为简、破茧成蝶

Swoole开源项目从2012年开始发布第一个版本,到现在已经有近7年的历史。在这七年的时间里:提交了8821次代码变更发布了287个版本收到并解决1161次issue反馈合并了603次pull request共有100位开发者贡献代码在GitHub收获了11940颗星协程2018年我们推出了全新的Swoole4版本,在此之前Swoole主要的编程方式还是同步阻塞模式或异步回调。新的基于协程实现的CSP编程逐渐成为我们唯一推荐使用的编程模式。协程将纷繁复杂异步编程大大简化。使用Swoole4协程,既简单又强大。在未来的Swoole5版本,我们计划删除非协程的相关特性和代码,减少历史包袱,提升稳定性,降低复杂度,减少不必要的选项,纯粹协程化。 过去前六我们的团队主要以兼职开发为主,团队成员大多来自于腾讯、阿里、滴滴、百度、360、小米等国内一线互联网企业,还有一部分是国外的PHP开发者,甚至PHP语言ZendVM内核作者Dmitry Stogov也曾向Swoole贡献了代码。除此之外,我们还招募了一些在校大学生为Swoole编写代码,逐步培养年轻一代开发者。在2018年7月份我们组建了全职开发团队,专注于Swoole内核以及Swoole Cloud云原生组件和生态链的开发。告别过去的草莽班子,转变为专业化的开源技术研发团队。我们的目标是让Swoole项目成为Node.js、Go这样的工业级技术,成为PHP编程语言的在异步IO和网络通信方面的基石。研发管理成立全职研发团队后,我们逐渐建立了非常完善的研发管理体系,提升Swoole的软件质量。主要包括以下几个方面:测试驱动(TDD)现在我们投入大量精力实现单元测试脚本、压测脚本、自动化测试,提升单元测试覆盖率。目前已有680项测试用例,17项压测项目,在Travis-CI平台可以看到每一次Commit和Pull Request的编译、测试结果。研发工作也基于TDD进行,在开发新特性、重构、Bug Fix时,会先编写对应的单元测试脚本,测试覆盖到代码变更的所有场景。代码审查(Code Review)团队成员之间进行代码交叉审查、互相Code Review,对于代码变更的细节进行充分的评估和讨论。重大变更,会进行团队Review,花费数小时甚至数天讨论每一行代码变更细节。RFC 机制对于非Bug Fix、非性能提升、非重构,新特性或有可能改变底层行为的变更,我们会分为4个步骤进行。发起RFC的提案,https://github.com/swoole/rfc…,提案内容会详细阐述此项变更的前因后果、相关配置项、影响的范围、使用方法、示例。提案讨论,我们会对提案进行充分的讨论,刨根问底,分析优劣,推敲细节。所有问题均讨论清楚后,最终立项,开始实现。开发负责人创建git分支,编写单元测试脚本,编写代码,实现提案中的所有内容,最终发起Pull Request交叉评审,检查代码,提出改进意见,反馈给开发负责人,继续完善细节。最终合并到主干。整个过程均是在GitHub平台公开进行的,对Swoole项目感兴趣的PHPer均可参与。灰度测试为了保证正式版本的稳定性,我们在发布前会在内部项目上进行灰度测试,检验新版本的稳定性。另外我们与大部分Swoole框架作者建立了联系,新版本会先发给各大框架的作者提前试用。有重大底层变更、或不兼容项会提前与其他Swoole之上的开源项目作者进行沟通。重构2018年下半年我们对底层的代码进行了多次重构,在代码结构、可读性、复用性、封装度方面进行了很多优化。使得Swoole软件更为简洁、优雅。编程语言方面,我们现在逐渐使用C++替代C语言。C++提供的面向对象、智能指针、容器、模板等特性能够帮助我们进一步提升我们团队的开发效率。在此也欢迎各位PHPer参与Swoole项目,贡献代码。文档Swoole的文档也是广为开发者诟病的一个方面。在2018年我们团队在文档方面逐渐加大投入。重新编写梳理文档,加入丰富的例子程序,加入更详细的配图,修复细节问题,删除带有感情色彩的语句,更加客观中立严谨。总结在过去的几年,Swoole项目做的并不是很专业,存在较多BUG和难用的地方,也让很多使用者踩到了不少坑。最近半年成立全职研发团队后,我们在研发管理方面进步飞快,Swoole的稳定性、成熟度方面已今非昔比。稳定性始终是第一位的,我们在未来将会更加谨慎、严谨,保证质量。2019 未来新的一年我们主要有3个方向上发力。做减法删除非协程的特性,删除不必要的模块,减少历史包袱,提升稳定性、降低复杂度,减少不必要的选项,化繁为简,更简单。Swoole内核层面仍然会继续不断重构、精简,减少代码行数,清理冗余代码,尽可能地实现代码复用。深入项目在2018年底,我们已经开始逐渐与在生产环境上大量使用Swoole的企业建立联系,包括腾讯云、阅文、好未来、陌陌、优信等企业。了解实际应用场景、业务模式,进行深度交流合作,提供建议,帮助企业技术团队更好的解决业务问题,接受反馈改进底层。生态链2019年我们会基于Swoole4协程开发一些配套的工具和组件,弥补PHP在Cloud Native时代生态链方面的不足。

January 21, 2019 · 1 min · jiezi

你与弄懂promise之间可能只差这篇文章(一)

promise诞生之前:因为JS引擎在执行js代码时只分配了一个线程去执行,所以Javascript是单线程的。由于有这个前置设定,前端er在书写代码时绕不开的一件事是就是—-如何处理异步,即处理“现在和稍后”关系的问题,事实上我们每一天都在与异步逻辑打交道。在promise出现之前,前端er基本上都是通过callback的方式来解决“稍后”的问题,例如有经典的“发布-订阅”模式,观察者模式,他们都运用了传入回调函数的高阶函数。vue2.x源码在实现数据双向绑定时就是运用的发布-订阅模式。我们先来看看三个例子。(例子均在node环境中运行, 其中name.txt中的内容是"kk", age.txt中的内容是10。)1 . 回调函数(callback)。fs读取文件的先后顺序是不固定的,我们无法判断哪个文件先读取完成。此例实现的是,在完全读取两个文件的内容之后进行某个操作(例如console个啥的)。let fs = require(‘fs’);let arr = [];let after = (times, cb) => { return (data) => { arr.push(data); if (–times === 0) { cb(arr) } }}let on = after(2, (arr) => { console.log(‘我是在全部读取了2个文件内容之后打印出来的, ‘, arr)})fs.readFile(’name.txt’, ‘utf8’, (err, data) => { on(data)})fs.readFile(‘age.txt’, ‘utf8’, (err, data) => { on(data)})结果: 我是在全部读取了2个文件内容之后打印出来的, [ ‘kk’, ‘10’ ]。说明: 这种写法的问题在于,需要依靠计数来执行回调函数里面的内容。我们先得这计算出有几个异步操作,然后统计出来在全部的异步操作完成后再执行回调。2 .发布-订阅模式。订阅的时候添加订阅者,发布的时候执行相应的订阅函数。此例实现的是,在特定的时候emit了某事件,订阅了该事件的回调函数继而执行。class EventEmitter { constructor () { this.subs = {} } on (eventName, cb) { if (!this.subs[eventName]) { this.subs[eventName] = [] } this.subs[eventName].push((…args) => cb(…args)) } emit (eventName, …args) { if (this.subs[eventName]) { this.subs[eventName].forEach(cb => cb(…args)) } else { throw Error(没有订阅${eventName}这个事件) } }}const event = new EventEmitter();let fs = require(‘fs’);event.on(‘kk-event’, (…args) => { fs.readFile(’name.txt’, ‘utf8’, (err, data) => { console.log(‘data1’, data, …args) })})event.on(‘kk-event’, (…args) => { fs.readFile(‘age.txt’, ‘utf8’, (err, data) => { console.log(‘data2’, data, …args) })})event.emit(‘kk-event’, 123, 456)结果:data1 kk 123 456data2 10 123 4563 . 观察者模式。它与发布-订阅两者本质是一样的,只不过观察者模式在写法上强调观察者和被观察者之间的关系,而发布-订阅模式则没有这样的关系。此例实现的是,在被观察者的状态发生变化后,观察者执行自己的update方法进行更新。 class Subject { constructor() { this.observers = []; this.state = ‘’; // 假设观察者观察的是被观察者的state } setState (status) { // 当state变化时出发观察者的update方法 this.state = status; this.notify(); } attach (observer) { this.observers.push(observer) // 与发布-订阅不同的是,这里添加的是一个个观察者实例,这就将被观察者和观察者之间关联了起来 } notify () { this.observers.forEach(observe => observe.update()) // 在被观察者状态变化时,调用更新的是观察者的update方法 }}class Observer { constructor (name, target) { this.name = name; this.target = target; } update () { console.log(通知${this.name},被观察者状态变化,所以观察者${this.name}跟着变化) }}let fs = require(‘fs’);let subject = new Subject();let observer1 = new Observer(‘kk1’, subject);let observer2 = new Observer(‘kk2’, subject);subject.attach(observer1);subject.attach(observer2);subject.setState(‘B’);结果:通知kk1,被观察者状态变化,所以观察者kk1跟着变化通知kk2,被观察者状态变化,所以观察者kk2跟着变化 ...

January 8, 2019 · 2 min · jiezi

Golang并发:除了channle,你还有其他选择

我们都知道Golang并发优选channel,但channel不是万能的,Golang为我们提供了另一种选择:sync。通过这篇文章,你会了解sync包最基础、最常用的方法,至于sync和channel之争留给下一篇文章。sync包提供了基础的异步操作方法,比如互斥锁(Mutex)、单次执行(Once)和等待组(WaitGroup),这些异步操作主要是为低级库提供,上层的异步/并发操作最好选用通道和通信。sync包提供了:Mutex:互斥锁RWMutex:读写锁WaitGroup:等待组Once:单次执行Cond:信号量Pool:临时对象池Map:自带锁的map这篇文章是sync包的入门文章,所以只介绍常用的结构和方法:Mutex、RWMutex、WaitGroup、Once,而Cond、Pool和Map留给大家自行探索,或有需求再介绍。互斥锁常做并发工作的朋友对互斥锁应该不陌生,Golang里互斥锁需要确保的是某段时间内,不能有多个协程同时访问一段代码(临界区)。互斥锁被称为Mutex,它有2个函数,Lock()和Unlock()分别是获取锁和释放锁,如下:type Mutexfunc (m *Mutex) Lock(){}func (m *Mutex) Unlock(){}Mutex的初始值为未锁的状态,并且Mutex通常作为结构体的匿名成员存在。经过了上面这么“官方”的介绍,举个例子:你在工商银行有100元存款,这张卡绑定了支付宝和微信,在中午12点你用支付宝支付外卖30元,你在微信发红包,抢到10块。银行需要按顺序执行上面两件事,先减30再加10或者先加10再减30,结果都是80,但如果同时执行,结果可能是,只减了30或者只加了10,即你有70元或者你有110元。前一个结果是你赔了,后一个结果是银行赔了,银行可不希望把这种事算错。看看实际使用吧:创建一个银行,银行里存每个账户的钱,存储查询都加了锁操作,这样银行就不会算错账了。银行的定义:type Bank struct { sync.Mutex saving map[string]int // 每账户的存款金额}func NewBank() *Bank { b := &Bank{ saving: make(map[string]int), } return b}银行的存取钱:// Deposit 存款func (b *Bank) Deposit(name string, amount int) { b.Lock() defer b.Unlock() if _, ok := b.saving[name]; !ok { b.saving[name] = 0 } b.saving[name] += amount}// Withdraw 取款,返回实际取到的金额func (b *Bank) Withdraw(name string, amount int) int { b.Lock() defer b.Unlock() if _, ok := b.saving[name]; !ok { return 0 } if b.saving[name] < amount { amount = b.saving[name] } b.saving[name] -= amount return amount}// Query 查询余额func (b *Bank) Query(name string) int { b.Lock() defer b.Unlock() if _, ok := b.saving[name]; !ok { return 0 } return b.saving[name]}模拟操作:小米支付宝存了100,并且同时花了20。func main() { b := NewBank() go b.Deposit(“xiaoming”, 100) go b.Withdraw(“xiaoming”, 20) go b.Deposit(“xiaogang”, 2000) time.Sleep(time.Second) fmt.Printf(“xiaoming has: %d\n”, b.Query(“xiaoming”)) fmt.Printf(“xiaogang has: %d\n”, b.Query(“xiaogang”))}结果:先存后花。➜ sync_pkg git:(master) ✗ go run mutex.goxiaoming has: 80xiaogang has: 2000也可能是:先花后存,因为先花20,因为小明没钱,所以没花出去。➜ sync_pkg git:(master) ✗ go run mutex.goxiaoming has: 100xiaogang has: 2000这个例子只是介绍了mutex的基本使用,如果你想多研究下mutex,那就去我的Github(阅读原文)下载下来代码,自己修改测试。Github中还提供了没有锁的例子,运行多次总能碰到错误:fatal error: concurrent map writes这是由于并发访问map造成的。读写锁读写锁是互斥锁的特殊变种,如果是计算机基本知识扎实的朋友会知道,读写锁来自于读者和写者的问题,这个问题就不介绍了,介绍下我们的重点:读写锁要达到的效果是同一时间可以允许多个协程读数据,但只能有且只有1个协程写数据。也就是说,读和写是互斥的,写和写也是互斥的,但读和读并不互斥。具体讲,当有至少1个协程读时,如果需要进行写,就必须等待所有已经在读的协程结束读操作,写操作的协程才获得锁进行写数据。当写数据的协程已经在进行时,有其他协程需要进行读或者写,就必须等待已经在写的协程结束写操作。读写锁是RWMutex,它有5个函数,它需要为读操作和写操作分别提供锁操作,这样就4个了:Lock()和Unlock()是给写操作用的。RLock()和RUnlock()是给读操作用的。RLocker()能获取读锁,然后传递给其他协程使用。使用较少。type RWMutexfunc (rw *RWMutex) Lock(){}func (rw *RWMutex) RLock(){}func (rw *RWMutex) RLocker() Locker{}func (rw *RWMutex) RUnlock(){}func (rw *RWMutex) Unlock(){}上面的银行实现不合理:大家都是拿手机APP查余额,可以同时几个人一起查呀,这根本不影响,银行的锁可以换成读写锁。存、取钱是写操作,查询金额是读操作,代码修改如下,其他不变:type Bank struct { sync.RWMutex saving map[string]int // 每账户的存款金额}// Query 查询余额func (b *Bank) Query(name string) int { b.RLock() defer b.RUnlock() if _, ok := b.saving[name]; !ok { return 0 } return b.saving[name]}func main() { b := NewBank() go b.Deposit(“xiaoming”, 100) go b.Withdraw(“xiaoming”, 20) go b.Deposit(“xiaogang”, 2000) time.Sleep(time.Second) print := func(name string) { fmt.Printf("%s has: %d\n", name, b.Query(name)) } nameList := []string{“xiaoming”, “xiaogang”, “xiaohong”, “xiaozhang”} for _, name := range nameList { go print(name) } time.Sleep(time.Second)}结果,可能不一样,因为协程都是并发执行的,执行顺序不固定:➜ sync_pkg git:(master) ✗ go run rwmutex.goxiaohong has: 0xiaozhang has: 0xiaogang has: 2000xiaoming has: 100等待组互斥锁和读写锁大多数人可能比较熟悉,而对等待组(WaitGroup)可能就不那么熟悉,甚至有点陌生,所以先来介绍下等待组在现实中的例子。你们团队有5个人,你作为队长要带领大家打开藏有宝藏的箱子,但这个箱子需要4把钥匙才能同时打开,你把寻找4把钥匙的任务,分配给4个队员,让他们分别去寻找,而你则守着宝箱,在这等待,等他们都找到回来后,一起插进钥匙打开宝箱。这其中有个很重要的过程叫等待:等待一些工作完成后,再进行下一步的工作。如果使用Golang实现,就得使用等待组。等待组是WaitGroup,它有3个函数:Add():在被等待的协程启动前加1,代表要等待1个协程。Done():被等待的协程执行Done,代表该协程已经完成任务,通知等待协程。Wait(): 等待其他协程的协程,使用Wait进行等待。type WaitGroupfunc (wg *WaitGroup) Add(delta int){}func (wg *WaitGroup) Done(){}func (wg *WaitGroup) Wait(){}来,一起看下怎么用WaitGroup实现上面的问题。队长先创建一个WaitGroup对象wg,每个队员都是1个协程, 队长让队员出发前,使用wg.Add(),队员出发寻找钥匙,队长使用wg.Wait()等待(阻塞)所有队员完成,某个队员完成时执行wg.Done(),等所有队员找到钥匙,wg.Wait()则返回,完成了等待的过程,接下来就是开箱。结合之前的协程池的例子,修改成WG等待协程池协程退出,实例代码:func leader() { var wg sync.WaitGroup wg.Add(4) for i := 0; i < 4; i++ { go follower(&wg, i) } wg.Wait() fmt.Println(“open the box together”)}func follower(wg *sync.WaitGroup, id int) { fmt.Printf(“follwer %d find key\n”, id) wg.Done()}结果:➜ sync_pkg git:(master) ✗ go run waitgroup.gofollwer 3 find keyfollwer 1 find keyfollwer 0 find keyfollwer 2 find keyopen the box togetherWaitGroup也常用在协程池的处理上,协程池等待所有协程退出,把上篇文章《Golang并发模型:轻松入门协程池》的例子改下:package mainimport ( “fmt” “sync”)func main() { var once sync.Once onceBody := func() { fmt.Println(“Only once”) } done := make(chan bool) for i := 0; i < 10; i++ { go func() { once.Do(onceBody) done <- true }() } for i := 0; i < 10; i++ { <-done }}单次执行在程序执行前,通常需要做一些初始化操作,但触发初始化操作的地方是有多处的,但是这个初始化又只能执行1次,怎么办呢?使用Once就能轻松解决,once对象是用来存放1个无入参无返回值的函数,once可以确保这个函数只被执行1次。type Oncefunc (o *Once) Do(f func()){}直接把官方代码给大家搬过来看下,once在10个协程中调用,但once中的函数onceBody()只执行了1次:package mainimport ( “fmt” “sync”)func main() { var once sync.Once onceBody := func() { fmt.Println(“Only once”) } done := make(chan bool) for i := 0; i < 10; i++ { go func() { once.Do(onceBody) done <- true }() } for i := 0; i < 10; i++ { <-done }}结果:➜ sync_pkg git:(master) ✗ go run once.goOnly once示例源码本文所有示例源码,及历史文章、代码都存储在Github:https://github.com/Shitaibin/golang_step_by_step/tree/master/sync_pkg下期预告这次先介绍入门的知识,下次再介绍一些深入思考、最佳实践,不能一口吃个胖子,咱们慢慢来,顺序渐进。下一篇我以这些主题进行介绍,欢迎关注:哪个协程先获取锁一定要用锁吗锁与通道的选择文章推荐Golang并发模型:轻松入门流水线模型Golang并发模型:轻松入门流水线FAN模式Golang并发模型:并发协程的优雅退出Golang并发模型:轻松入门selectGolang并发模型:select进阶Golang并发模型:轻松入门协程池Golang并发的次优选择:sync包如果这篇文章对你有帮助,请点个赞/喜欢,感谢。本文作者:大彬如果喜欢本文,随意转载,但请保留此原文链接:http://lessisbetter.site/2019/01/04/golang-pkg-sync/ ...

January 5, 2019 · 3 min · jiezi

js异步机制

前言js引擎不是独立运行的,它运行在宿主环境中,这个环境可以是浏览器、可以是服务器,或者其他硬件设施。所以在浏览器的帮助下,js作为一种单线程语言,可以实现异步操作。浏览器内核是多线程的,几个常驻的线程:渲染引擎线程、js引擎线程、定时触发器线程、事件触发线程、异步http请求线程。并发模型https://developer.mozilla.org…左边的栈存储的是同步任务。右边的堆用来存储声明的变量、对象。下面的队列就是任务队列,一旦某个异步任务有了响应就会被推入队列中。每个异步任务都和一个回调函数相关联。一个js程序的单线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取任务队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务,执行完毕。单线程从任务队列中读取任务是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有新的任务,就会等待,直到有新的任务,这就叫任务循环或者事件循环事件循环(Event Loop)事件循环的大致流程如下:主线程执行所有同步任务,形成一个执行栈(并发模型中的stack)。主线程执行同步任务的同时,子线程执行异步任务,并将相应的结果(事件)放入任务队列。一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,把任务队列中事件相应的回调函数压入栈内开始执行。执行回调后,栈空,继续重复第三步,形成一个循环。Microtask与Macrotask(task)Microtask和Macrotask(task)是异步任务的两个分类。macrotasks: setTimeout, setInterval, setImmediate, I/O, UI renderingmicrotasks: process.nextTick, Promises, Object.observe(废弃), MutationObserver事件循环每次只会入栈一个 macrotask任务 ,主线程会先检查 microtasks 队列并完成里面的所有任务后再执行 macrotask(task)。举个栗子:console.log(‘script start’);setTimeout(function() { console.log(‘setTimeout1’);})}, 0);setTimeout(function() { console.log(‘setTimeout2’);}, 0);Promise.resolve().then(function() { console.log(‘promise1’);}).then(function() { console.log(‘promise2’);});console.log(‘script end’);结果是:“script start"“script end"“promise1"“promise2"“setTimeout1"“setTimeout2"可以看到,macrotask是在microtask全部执行后才执行的。再举一个栗子:console.log(‘script start’);setTimeout(function() { console.log(‘setTimout1’); setTimeout(function() { console.log(‘setTimout2’); }, 0); Promise.resolve().then(function() { console.log(‘promise3’); })}, 0);Promise.resolve().then(function() { console.log(‘promise1’);}).then(function() { console.log(‘promise2’);});console.log(‘script end’);结果是:“script start"“script end"“promise1"“promise2"“setTimout1"“promise3"“setTimout2"可以发现,在执行完setTimeout1之后没有继续执行setTimeout2,而是去执行了promise3,这也验证了:每执行一个macrotask任务之前都会执行完当前的microtask队列。注: 代码示例参考链接:https://v.youku.com/v_show/id…

December 26, 2018 · 1 min · jiezi

PHP协程:并发 shell_exec

在PHP程序中经常需要用shell_exec执行一些命令,而普通的shell_exec是阻塞的,如果命令执行时间过长,那可能会导致进程完全卡住。在Swoole4协程环境下可以用Co::exec并发地执行很多命令。本文基于Swoole-4.2.9和PHP-7.2.9版本协程示例<?php$c = 10;while($c–) { go(function () { //这里使用 sleep 5 来模拟一个很长的命令 co::exec(“sleep 5”); });}协程结果htf@htf-ThinkPad-T470p:/workspace/debug$ time php t.phpreal 0m5.089suser 0m0.067ssys 0m0.038shtf@htf-ThinkPad-T470p:/workspace/debug$只用了 5秒,程序就跑完了。下面换成 PHP 的 shell_exec 来试试。阻塞代码<?php$c = 10;while($c–) { //这里使用 sleep 5 来模拟一个很长的命令 shell_exec(“sleep 5”);}阻塞结果htf@htf-ThinkPad-T470p:/workspace/debug$ time php s.php real 0m50.119suser 0m0.066ssys 0m0.058shtf@htf-ThinkPad-T470p:/workspace/debug$ 可以看到阻塞版本花费了50秒才完成。Swoole4提供的协程,是并发编程的利器。在工作中很多地方都可以使用协程,实现并发程序,大大提升程序性能。

November 29, 2018 · 1 min · jiezi

从时间碎片角度理解阻塞IO模型及非阻塞模型

阻塞模型限制了服务器的并发处理能力(伸缩性或同时处理的客户端连接数)传统的网络服务器只支持阻塞模型,该模型下,针对每个客户端连接,服务器都必须创建一个线程来处理这个连接上的请求,服务器必须维持着这些线程直到线程中的处理工作结束。服务器上所能创建的线程数量是有限的,WHY?进程上下文切换是耗时的过程创建的进程本身占用资源,比如每个进程或线程占用一定容量的内存等待数据准备和内核缓存复制,导致IO阻塞,占用着线程所以当连接到服务器上的客户端的数量很大时,把服务器上所能创建的线程都占据了时,服务器就无法接受更多的连接了。这限制了服务器处理请求的伸缩性。并非所有客户端都是持续活跃的存在这样一个事实,就是虽然连接到服务器上的客户端很多,但并非所有客户端都是持续活跃着的。它们占据着阻塞式服务器的线程资源——即使它们处于非工作状态。这些线程占据了资源,却不工作。这会造成什么现象呢?就是线程时间的碎片化——一个线程大部分时间是在等待IO操作的结果。为了让服务器能接受更多客户端的连接,非阻塞模型就出现了。如何提升服务器的并发处理能力?消灭碎片化时间,可以提升服务器的并发处理能力。如何消灭碎片化时间? 让线程分工协作各司其职,是一个很好的手段。原来的阻塞模型下,一个线程要干所有的事情。分工协作机制下,一部分线程专门用于接受客户端的连接、一部分专门用于获取请求的数据、一部分专门执行计算工作、还有一部分线程专门用于响应客户端。接受客户端连接的线程在接收到客户端连接后,立即把连接交给后续工序的线程处理,而它自己则继续接受下一个连接。如此类推,各个线程无须等待,不存在碎片化时间,全负荷工作。这样一来,整体上需要的较少的线程,就可以完成以前需要较多线程才能达到的工作时间了。阻塞模型下的实现方式在阻塞模型下,利用异步处理的方式对线程进行分工协作。接收请求的线程可以满负荷工作,但处理IO操作的线程仍然是阻塞着的,仍然存在线程工作不饱和的现象。非阻塞模型彻底消灭线程工作不饱和非阻塞模型下,IO操作不再是阻塞的了,而是立即返回。这样的话,处理IO操作的线程,可以在空闲时对所有请求进行轮询,以便判断哪些IO操作已完成。比如判断某个请求是否可以进行“写”操作,如果还不可以,无须等待,继续判断下一个请求是否可以进行“读”操作,如果可以则立即读取数据,然后把数据转交给专职计算的线程。这样就让线程工作不饱和现象消失了。这是所谓的“同步非阻塞”。轮询的耗时如何消灭?这就要请出“IO复用”这尊大神了。IO复用模型下,线程一次性从操作系统那儿获得一批可以进行IO操作的请求,处理完毕后,再此获得新的一批。线程无须与操作系统交互多次以便轮询每个请求的状态,而是与操作系统交互一次即可获得批量信息。效率进一步提高啦。

November 14, 2018 · 1 min · jiezi

PHP下的异步尝试二:初识协程

PHP下的异步尝试系列如果你还不太了解PHP下的生成器,你可以根据下面目录翻阅PHP下的异步尝试一:初识生成器PHP下的异步尝试二:初识协程PHP下的异步尝试三:协程的Co自动执行器 [待开发]PHP下的异步尝试四:PHP版的Promise [待开发]…多任务 (并行和并发)在讲协程之前,先谈谈多进程、多线程、并行和并发。对于单核处理器,多进程实现多任务的原理是让操作系统给一个任务每次分配一定的 CPU 时间片,然后中断、让下一个任务执行一定的时间片接着再中断并继续执行下一个,如此反复。由于切换执行任务的速度非常快,给外部用户的感受就是多个任务的执行是同时进行的。多进程的调度是由操作系统来实现的,进程自身不能控制自己何时被调度,也就是说: 进程的调度是由外层调度器抢占式实现的而协程要求当前正在运行的任务自动把控制权回传给调度器,这样就可以继续运行其他任务。这与抢占式的多任务正好相反, 抢占多任务的调度器可以强制中断正在运行的任务, 不管它自己有没有意愿。如果仅依靠程序自动交出控制的话,那么一些恶意程序将会很容易占用全部 CPU 时间而不与其他任务共享。协程的调度是由协程自身主动让出控制权到外层调度器实现的回到刚才生成器实现 xrange 函数的例子,整个执行过程的交替可以用下图来表示:协程可以理解为纯用户态的线程,通过协作而不是抢占来进行任务切换。相对于进程或者线程,协程所有的操作都可以在用户态而非操作系统内核态完成,创建和切换的消耗非常低。简单的说协程 就是提供一种方法来中断当前任务的执行,保存当前的局部变量,下次再过来又可以恢复当前局部变量继续执行。我们可以把大任务拆分成多个小任务轮流执行,如果有某个小任务在等待系统 IO,就跳过它,执行下一个小任务,这样往复调度,实现了 IO 操作和 CPU 计算的并行执行,总体上就提升了任务的执行效率,这也便是协程的意义多线程在单核下,多线程必定是并发的;不过现在的统一进程的多线程是可以运行在多核CPU下,所以可以是并行的并发(Concurrency)是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。并行(Parallesim)是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。多个操作可以在重叠的时间段内进行。并行和并发区别并发指的是程序的结构,并行指的是程序运行时的状态并行一定是并发的,并行是并发设计的一种单线程永远无法达到并行状态协程协程的支持是在生成器的基础上, 增加了可以回送数据给生成器的功能(调用者发送数据给被调用的生成器函数). 这就把生成器到调用者的单向通信转变为两者之间的双向通信.我们在上篇文章已经讲过了send方法, 下面让我们理解下协程同步代码在没有涉及到异步执行代码之前,我们的代码都是这样的function printNum($max, $caller){ for ($i=0; $i<$max; $i++ ) { echo “调度者:” . $caller . " 打印:" . $i . PHP_EOL; }}printNum(3, “caller1”);printNum(3, “caller2”);# output调度者:caller1 打印:0调度者:caller1 打印:1调度者:caller1 打印:2调度者:caller2 打印:0调度者:caller2 打印:1调度者:caller2 打印:2使用协程后改进的代码初稿,手动调整生成器执行# 本代码手动调整了进程执行代码的顺序,当然本代码实现不用协程也可以,只是利用本流程说明协程作用# 生成器给了我们函数中断,协程[生成器send]给了我们重新唤起生成器函数的能力function printNumWithGen($max){ for ($i=0; $i<$max; $i++ ) { $res = yield $i; echo $res; }}$gen1 = printNumWithGen(3);$gen2 = printNumWithGen(3);// 手动执行caller1 再 caller2$gen1->send(“调度者: caller1 打印:” . $gen1->current() . PHP_EOL);$gen2->send(“调度者: caller2 打印:” . $gen2->current() . PHP_EOL);// 手动执行caller1 再 caller2$gen1->send(“调度者: caller1 打印:” . $gen1->current() . PHP_EOL);$gen2->send(“调度者: caller2 打印:” . $gen2->current() . PHP_EOL);// 手动执行caller2 再 caller1$gen2->send(“调度者: caller2 打印:” . $gen2->current() . PHP_EOL);$gen1->send(“调度者: caller1 打印:” . $gen1->current() . PHP_EOL);# output调度者: caller1 打印:0调度者: caller2 打印:0调度者: caller1 打印:1调度者: caller2 打印:1调度者: caller2 打印:2调度者: caller1 打印:2总结上面案例应该让大家理解了协程设计的意义和如何使用协程那么接下去我们为我们的协程自动一个自动调度器(Co自动执行器),无需再手动来中断和恢复了 ...

September 20, 2018 · 1 min · jiezi

Swoole 4.1.0 正式版发布,支持原生 Redis/PDO/MySQLi 协程化

重大新特性支持 Redis/PDO/MySQLi从4.1.0版本开始支持了对PHP原生Redis、PDO、MySQLi协程化的支持。可使用SwooleRuntime::enableCorotuine()将普通的同步阻塞Redis、PDO、MySQLi操作变为协程调度的异步非阻塞IOSwooleRuntime::enableCoroutine();go(function () { $redis = new redis; $retval = $redis->connect(“127.0.0.1”, 6379); var_dump($retval, $redis->getLastError()); var_dump($redis->get(“key”)); var_dump($redis->set(“key”, “value”)); $redis->close();});协程跟踪新版本增加了两个方法用于跟踪协程运行。Coroutine::listCoroutines()可遍历当前所有协程Coroutine::getBackTrace($cid)可获取某个协程的函数调用栈function test1() { test2();}function test2() { while(true) { co::sleep(10); echo FUNCTION." n"; }}$cid = go(function () { test1();});go(function () use ($cid) { while(true) { echo “BackTrace[$cid]:n———————————————–n”; //返回数组,需要自行格式化输出 var_dump(co::getBackTrace($cid)).“n”; co::sleep(3); }});BackTrace[1]:———————————————–#0 SwooleCoroutine::sleep(10) called at [/home/htf/workspace/swoole/examples/coroutine/backtrace.php:8]#1 test2() called at [/home/htf/workspace/swoole/examples/coroutine/backtrace.php:3]#2 test1() called at [/home/htf/workspace/swoole/examples/coroutine/backtrace.php:14]其他修改重构 CoChannel C底层代码为C++, 解决复杂场景的非预期结果, 实现高稳定重构 CoHttpClient C底层代码为C++协程模式, 解决异步时序问题, 实现高稳定支持在协程和Server中使用exit, 此时将会抛出可捕获的SwooleExitException异常移除所有迭代器(table/connection/coroutine_list)的PCRE依赖限制增加open_websocket_close_frame配置, 可以在onMessage事件中接收close帧废弃HttpResponse->gzip()方法,改为使用http_compression配置项。底层会自动判断客户端传入的Accept-Encoding选择合适的压缩方法, 新增谷歌BR压缩支持增加CoHttpClient->addData()方法,可将内存中的数据作为上传文件内容进行发送Solaris系统支持Http2支持MAX_FRAME_SIZE分帧发送和MAX_HEADER_LIST_SIZE处理, 客户端增加isStreamExist方法检测是否存在对应流swoole_http_response->status增加reason参数修复MySQL prepare 中无符号参数使用了有符号值导致数值溢出的问题修复HTTP2的onRequest回调中没有协程的问题修复tasking_num某些特殊情况下变为-1的问题修复HTTP2-server的window-update帧构造错误修复所有PHP版本下的所有级别的编译warningGCC版本小于4.8时将会产生编译错误修复MySQL使用prepare时未使用参数绑定导致的内存分配不断增长修复HTTP2重连时旧stream内存丢失泄露底层开发相关统一文件命名 #970CoHttpClient使用了create_obj和free_obj保证内存安全, 防止错误的PHP代码引发内存问题 ...

September 1, 2018 · 1 min · jiezi