当前位置: 首页 > 科技观察

异步编程的几种方法全解析

时间:2023-03-18 00:16:23 科技观察

前端技术发展迅猛。过去的最佳实践在未来可能会被新方法所取代。只有不断学习,才能不那么迷茫。异步编程一直是JavaScript中的一个重点。比较难的部分也是面试中的高频试题。在JavaScript中,异步编程的方法一直在变化。到目前为止,异步编程的方法有6种。学习和掌握这些方法并不容易。本文通过一个生动的例子来讲解异步编程的发展历史和6种异步编程。方法,实践出真知。前言很久以前(大概2015年之前),说到异步,只有回调。ES2015带来了Promise和Generator,ES2017带来了更高级的async/await,今年发布的ES2022带来了顶级await。只是不需要在async函数中,直接使用await即可。作为前端人员,并没有遇到太多复杂的异步场景。对于requests和events,大部分场景都太简单了,只有动画是唯一需要考虑的。本文将通过不同的方式实现以下动画,红色方块每次向右移动100像素,完整的demo在这里。关于AsyncAsync是一个关于现在和未来的问题,即现在执行的代码和将来要执行的代码。举个例子说明异步是多么复杂和反人类:vara=1;varb=1;functionfoo(){a=b+3;控制台日志(一);}函数bar(){b=a*2;}ajax("url1",foo);ajax("url2",栏);上面的代码如果是同步的,那么打印a的地方是确定的;但是如果是异步的,a的值有两种可能(取决于哪个请求先返回),这还是最简单的情况,大家可以自己想一想,写程序的时候有没有考虑过这类问题?异步编程方法JavaScript中有6种异步编程方法。大多数学生不应该全部理解。其中,事件监听和观察者有些相似。下面我们分别介绍一下:回调函数观察者PromiseGeneratorasync/await回调函数callback曾经是我们最喜欢的方式,上面提到的动画例子,使用回调实现代码如下:moveTo(100,0,function(){moveTo(200,0,function(){moveTo(300,0,function(){moveTo(400,0,function(){//没有循环的无限});});});});看看,这就是传说中的回调地狱吗?是和不是。..可能有同学会问moveTo函数是怎么实现的?答案如下:functionmoveTo(x=0,y=0,cb=function(){}){move(".box").x(x).y(y).end(cb);}下面列出回调的N大罪孽,有悖常理,错误跟踪模拟同步回调地狱,并发执行信任问题(多次调用)有悖常理,不说缩进,缩进其实可以通过拆分函数来解决,但是人性化的顺序执行,现在跳来跳去。错误追踪,异步让trycatch直接跪着,为了能够捕捉到异步错误,有两种方案,单独回调和先error。jquery的ajax是典型的分离回调,示例代码如下:functionsuccess(data){console.log(data);}functionerror(err){console.error(err);}$.ajax({},成功,错误);Node.js初始异步接口使用firsterror,第一个参数为error对象,代码示例如下:functioncallback(err,data){if(err){//errorreturn;}//成功console.log(data);}async("url",callback);让我们看看模拟的同步问题。Node的io相关的API都是异步的。我只想要一个像python这样的同步脚本。这并不容易,让我们看一个例子。一段很容易理解的同步代码:for(leti=0;i{cb(...rest);});}functionsub(msg,cb){eventMap[msg]=eventMap[msg]||[];eventMap[msg].push(cb);}Promise下面是上面例子的Promise实现:moveTo(100,0).then(function(){returnmoveTo(200,0);}).then(function(){returnmoveTo(300,0);}).then(function(){returnmoveTo(400,0);}).then(function(){//没有循环的无限});我们再来看看,moveTo函数有什么区别,你看出来了吗?functionmoveTo(x=0,y=0){returnnewPromise(function(resolve,reject){move(".box").x(x).y(y).end(resolve);});}第一次接触Promise,你可能认为这不是回调?请再次注意,这里的回调不是针对异步的,而是针对Promise协议的。Promise其实是一种控制反转。比如我们以前是给异步函数传一个回调函数,现在变成异步函数返回一个Promise对象,堪称神来之笔,而Promise就是实现这种反转的工具。Promise是双方约定的契约(规范)。事实上,Promise有很多优点。要知道Promise是未来新技术的基础。它可以称为所有异步解决方案的粘合剂。没有Promise,可能就没有Generator,那为什么可能呢?请参阅生成器部分。Promise解决了回调的一些问题,但不是全部。比如Promise有很好的错误跟踪,避免回调地狱,对并发执行非常友好,因为Promise只解析一次,很好的解决了信任问题。但是Promises并不是违反直觉的友好,回调变成了Promises的长链。看一下模拟同步的代码,好像解决成功了:varchain=Promise.resolve();for(leti=0;iasync(arr[i]).then((x)=>(arr[i]=x)));}GeneratorGenerator是一个革命性的特性,在ES2015(ES6)中引入,使得原来的函数必须执行一次,现在可以中途暂停,下次继续执行,这让js模拟了协程的概念。其实一开始我是拒绝Generator的。这个东西不是为异步而生的,而是为了产生不断产生的数据。和迭代器天生一对,用它做异步使用的好处和布局用float类似(float本来只是为了达到文字换行的效果),有点绕,不太好理解。动画示例使用生成器实现如下,就好像它是同步的:function*gen(){yieldmoveTo(100,0);屈服移动(200,0);屈服移动(300,0);屈服移动(400,0);//无限非循环}run(gen);moveTo函数如下:functionmoveTo(x=0,y=0){returnnewPromise(function(resolve,reject){move(".box").x(x).y(y).end(resolve);});}看,多么整洁和完美,但我发誓当你看到run函数时你会发疯的。函数运行(fn){让gen=fn();functionnext(data){varres=gen.next(data);如果(res.done)返回res.value;res.value.then(function(data){next(data);});}next();}要实现异步,生成器必须有一个启动函数,即run。好在社区有打包运行的功能,比如co库。生成器擅长解决Promises无法解决的问题,例如违反直觉、代码繁琐,但它们也有自己的问题,例如不易理解、蹩脚、需要入门。在Promise部分,我说如果没有Promise,可能就没有Generator,没必要吧?因为Generator实现异步不仅仅基于Promise,还基于thunk函数。如果您有兴趣,请自行查看。祝你好运。async/await那么有没有完美的解决方案呢?真的,ES2017(ES8)给我们带来了异步函数,这家伙简直就是一个自带starter的生成器函数,好吧,他就是为了Generator。动画示例是使用异步函数实现的:asyncfunctionrun(){awaitmoveTo(100,0);等待moveTo(200,0);等待moveTo(300,0);等待moveTo(400,0);//无限无循环}run();moveTo函数如下:asyncfunctionmoveTo(x=0,y=0){awaitnewPromise(function(resolve,reject){move(".box").x(x).y(y).end(resolve);});}完美同步代码,内置启动器,世界终于回归美好,终极异步解决方案,你想学什么生成器。总结本文介绍了JavaScript中异步编程的所有方法。希望能帮助大家更好的理解异步编程,选择正确的方法。最后用网上的一张图来总结总结一下区别: