异步编程的六种方式

您所在的位置:网站首页 实验数据的处理方法有哪几种形式 异步编程的六种方式

异步编程的六种方式

2024-07-06 01:31| 来源: 网络整理| 查看: 265

文章目录 前言`1、回调函数的方式``2、Promise对象`3、生成器函数 Generator/ yield4、async/await 函数的实现`5、事件监听``6、发布/订阅`

前言

你可能知道,Javascript语言的执行环境是"单线程"(single thread)。

所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。

单线程的好处是实现起来比较简单,执行环境相对单纯; 坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。

常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,Javascript语言将任务的执行模式分成两种:

同步(Synchronous)异步(Asynchronous)

"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;

"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。 在服务器端,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

以下是异步编程的六种方式:

1、回调函数的方式

这是异步编程最基本的方法。

所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数

// 异步请求 ajax(url, () => { // 请求返回后的处理逻辑 }) // 读取文件 fs.readFile('/etc/shells', function (err, data) { // 文件读取完毕后的操作 console.log(data); });

回调函数有一个致命的弱点,就是容易写出回调地狱

ajax(url, () => { // 处理逻辑 ajax(url1, () => { // 处理逻辑 ajax(url2, () => { // 处理逻辑 }) }) }) 优点 是简单、容易理解和部署缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱。 2、Promise对象

Promise就是为了解决回调地狱而产生的,将回调函数的嵌套,改成链式调用。

简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。

例子如下:

function A(n) { return new Promise(resolve => { setTimeout(() => resolve(n + 200), n); }); } function step1(n) { console.log(`step1 with ${n}`); return A(n); } function step2(n) { console.log(`step2 with ${n}`); return A(n); } function step3(n) { console.log(`step3 with ${n}`); return A(n); } step1(100).then( time2 =>step2(time2)) .then( time3 => step3(time3)) .then( result => { console.log(`result is ${result}`) });

优点:回调函数变成了链式写法,每个异步函数执行完成后,才会去执行下一个then函数; 缺点:Promise的写法只是回调函数的改进,用then()方法免去了嵌套,更为直观。但这样写也存在了很明显的问题,代码变得冗杂了,语义化并不强。

3、生成器函数 Generator/ yield

Generator 函数是 ES6 提供的一种异步编程解决方案:

yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化。yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。每个yield返回的是{value:yield返回的值,done:true/false(执行状态)}

Generator的原理

generator 的方式,它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部还可以将执行权转移回来。

执行过程中,当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。因此在 generator 内部对于异步操作的方式,可以以同步的顺序来书写。

使用这种方式需要考虑的问题是何时将函数的控制权转移回来,因此需要有一个自动执行 generator 的机制,比如说 co 模块等方式来实现 generator 的自动执行。

// 异步函数 function ajax(duration) { return new Promise((resolve,reject)=>{ setTimeout(()=>{ console.log(duration+'ms延时打印输出!,当前位于'+ new Date().getSeconds() +'秒') resolve(duration); },duration) }) } // 生成器函数 function* main() { const user = yield ajax(2000); console.log('user',user); const posts = yield ajax(10000); console.log(posts); } // 封装执行生成器函数 function co(generator) { const g = generator(); function handleResult(result) { if(result.done) return; //生成器函数结束 result.value.then(data => { //这里与yield的返回有关 console.log('继续执行!',data); handleResult(g.next(data)); },error => { g.throw(error); } ); } handleResult(g.next()); } co(main);

输出结果如下: 在这里插入图片描述

4、async/await 函数的实现

为了解决 Promise 的问题,async、await 在 ES7 中被提了出来,是目前为止最好的解决方案。

async 是“异步”的意思,而 await 是等待的意思。所以应该很好理解 async 用于申明一个 异步的function(实际上是async function 对象)await 用于等待一个异步任务执行完成的的结果。并且 await 只能出现在 async 函数中。

async/await 异步编程的解决原理

async 函数是通过 generator 和 promise 实现的一个自动执行的语法糖,当函数内部执行到一个await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写。

async function test(){ let newTime = await new Promise((resolve,reject)=>{ //这里等待异步返回结果,再继续向下执行 let time = 3000; setTimeout(()=>{ resolve(time); },time) }) console.log(newTime+'毫秒后执行'); let content = 'test'; console.log(content); //3s后,先输出 “3000毫秒后执行”,再输出 "test" } test() async函数 ——返回的是一个 Promise 对象 async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result); // async 函数返回的是一个Promise对象 result.then(v=>{ console.log(v); }) console.log(123) // 先输出promise对象,其次123,最后hello async

在这里插入图片描述

async/await的参考链接

5、事件监听

这种方式下,异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

下面是两个函数f1和f2,编程的意图是f2必须等到f1执行完成,才能执行。

首先,为f1绑定一个事件(jQuery写法)

f1.on('done', f2);

上面这行代码的意思是,当f1发生done事件,就执行f2。然后,对f1进行改写:

function f1() { setTimeout(function () { // ... f1.trigger('done'); }, 1000); }

上面代码中,f1.trigger(‘done’)表示,执行完成后,立即触发done事件,从而开始执行f2

优点:是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合",有利于实现模块化。 缺点:是整个程序都要变成事件驱动型,运行流程会变得很不清晰。

6、发布/订阅

我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)

下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件

首先,f2向"信号中心"jQuery订阅"done"信号

jQuery.subscribe("done", f2);

f1进行如下改写:

function f1(){   setTimeout(function () {     // f1的任务代码     jQuery.publish("done");   }, 1000); }

jQuery.publish(“done”)的意思是,f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。

此外,f2完成执行后,也可以取消订阅(unsubscribe)

jQuery.unsubscribe("done", f2);

更多参考链接:https://blog.csdn.net/yiyueqinghui/article/details/111057634

参考链接:https://www.jianshu.com/p/d7f6077a0dd2



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3