SpringBoot多线程进行异步请求的处理方式

目录
  • SpringBoot多线程进行异步请求的处理
    • 第一步:编写配置类
    • 第二步:对方法使用注解标注为使用多线程进行处理
  • SpringBoot请求线程优化
    • 使用Callable来实现
    • 1、异步调用的另一种方式
    • 3、Deferred方式实现异步调用

SpringBoot多线程进行异步请求的处理

近期在协会博客园中,有人发布了博客,系统进行查重的时候由于机器最低配置进行大量计算时需要十秒左右时间才能处理完,由于一开始是单例模式,导致,在某人查重的时候整个系统是不会再响应别的请求的,导致了系统假死状态,那么我们就应该使用多线程进行处理,将这种不能快速返回结果的方法交给线程池进行处理。

而我们自己使用Java Thread实现多线程又比较麻烦,在这里我们使用SpringBoot自带的多线程支持,仅需两步就可以对我们的查重方法利用多线程进行处理

第一步:编写配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;

@Configuration
@EnableAsync  // 启用异步任务
public class AsyncConfig {

    // 声明一个线程池(并指定线程池的名字)
    @Bean("AsyncThread")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程数5:线程池创建时候初始化的线程数
        executor.setCorePoolSize(5);
        //最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(10);
        //缓冲队列500:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        //允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("AsyncThread-");
        executor.initialize();
        return executor;
    }
}

第二步:对方法使用注解标注为使用多线程进行处理

并将方法改写返回类型(因为不能立即返回,所以需要将返回值改为CompletableFuture<String>,

返回的时候使用return CompletableFuture.completedFuture(str);)

方法示范:

    @Async("AsyncThread")
    @RequestMapping(value = "/addBlog")
    public CompletableFuture<String> addBlog(HttpSession httpSession, Blog blog, String blogId, boolean tempSave) {
        System.out.println("\n\n----------------------------------------------");
        System.out.println(Thread.currentThread().getName() + "正在处理请求");
        System.out.println("----------------------------------------------");
        String result = "请求失败";
        //....你的业务逻辑
        return CompletableFuture.completedFuture(result);
    }

这样以后你的这个方法将会交由线程池去进行处理,并将结果返回,一定要记得改返回值类型,否则返回的将是空的。

SpringBoot请求线程优化

在我们的实际生产中,常常会遇到下面的这种情况,某个请求非常耗时(大约5s返回),当大量的访问该请求的时候,再请求其他服务时,会造成没有连接使用的情况,造成这种现象的主要原因是,我们的容器(tomcat)中线程的数量是一定的,例如500个,当这500个线程都用来请求服务的时候,再有请求进来,就没有多余的连接可用了,只能拒绝连接。要是我们在请求耗时服务的时候,能够异步请求(请求到controller中时,则容器线程直接返回,然后使用系统内部的线程来执行耗时的服务,等到服务有返回的时候,再将请求返回给客户端),那么系统的吞吐量就会得到很大程度的提升了。当然,大家可以直接使用Hystrix的资源隔离来实现,今天我们的重点是spring mvc是怎么来实现这种异步请求的。

使用Callable来实现

controller如下:

    @RestController
    public class HelloController {undefined
        private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
        @Autowired
        private HelloService hello;

        @GetMapping("/helloworld")
        public String helloWorldController() {undefined
            return hello.sayHello();
        }

        /**
         * 异步调用restful
         * 当controller返回值是Callable的时候,springmvc就会启动一个线程将Callable交给TaskExecutor去处理
         * 然后DispatcherServlet还有所有的spring拦截器都退出主线程,然后把response保持打开的状态
         * 当Callable执行结束之后,springmvc就会重新启动分配一个request请求,然后DispatcherServlet就重新
         * 调用和处理Callable异步执行的返回结果, 然后返回视图
         *
         * @return
         */
        @GetMapping("/hello")
        public Callable<String> helloController() {undefined
            logger.info(Thread.currentThread().getName() + " 进入helloController方法");
            Callable<String> callable = new Callable<String>() {undefined

                @Override
                public String call() throws Exception {undefined
                    logger.info(Thread.currentThread().getName() + " 进入call方法");
                    String say = hello.sayHello();
                    logger.info(Thread.currentThread().getName() + " 从helloService方法返回");
                    return say;
                }
            };
            logger.info(Thread.currentThread().getName() + " 从helloController方法返回");
            return callable;
        }
    }

我们首先来看下上面这两个请求的区别:

下面这个是没有使用异步请求的

2017-12-07 18:05:42.351 INFO 3020 --- [nio-8060-exec-5] c.travelsky.controller.HelloController : http-nio-8060-exec-5 进入helloWorldController方法
2017-12-07 18:05:42.351 INFO 3020 --- [nio-8060-exec-5] com.travelsky.service.HelloService : http-nio-8060-exec-5 进入sayHello方法!
2017-12-07 18:05:44.351 INFO 3020 --- [nio-8060-exec-5] c.travelsky.controller.HelloController : http-nio-8060-exec-5 从helloWorldController方法返回

我们可以看到,请求从头到尾都只有一个线程,并且整个请求耗费了2s钟的时间。

下面,我们再来看下使用Callable异步请求的结果:

2017-12-07 18:11:55.671 INFO 6196 --- [nio-8060-exec-1] c.travelsky.controller.HelloController : http-nio-8060-exec-1 进入helloController方法
2017-12-07 18:11:55.672 INFO 6196 --- [nio-8060-exec-1] c.travelsky.controller.HelloController : http-nio-8060-exec-1 从helloController方法返回
2017-12-07 18:11:55.676 INFO 6196 --- [nio-8060-exec-1] c.t.i.MyAsyncHandlerInterceptor : http-nio-8060-exec-1 进入afterConcurrentHandlingStarted方法
2017-12-07 18:11:55.676 INFO 6196 --- [ MvcAsync1] c.travelsky.controller.HelloController : MvcAsync1 进入call方法
2017-12-07 18:11:55.676 INFO 6196 --- [ MvcAsync1] com.travelsky.service.HelloService : MvcAsync1 进入sayHello方法!
2017-12-07 18:11:57.677 INFO 6196 --- [ MvcAsync1] c.travelsky.controller.HelloController : MvcAsync1 从helloService方法返回
2017-12-07 18:11:57.721 INFO 6196 --- [nio-8060-exec-2] c.t.i.MyAsyncHandlerInterceptor : http-nio-8060-exec-2服务调用完成,返回结果给客户端

从上面的结果中,我们可以看出,容器的线程http-nio-8060-exec-1这个线程进入controller之后,就立即返回了,具体的服务调用是通过MvcAsync2这个线程来做的,当服务执行完要返回后,容器会再启一个新的线程http-nio-8060-exec-2来将结果返回给客户端或浏览器,整个过程response都是打开的,当有返回的时候,再从server端推到response中去。

1、异步调用的另一种方式

上面的示例是通过callable来实现的异步调用,其实还可以通过WebAsyncTask,也能实现异步调用,下面看示例:

    @RestController
    public class HelloController {undefined
        private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
        @Autowired
        private HelloService hello;
            /**
         * 带超时时间的异步请求 通过WebAsyncTask自定义客户端超时间
         *
         * @return
         */
        @GetMapping("/world")
        public WebAsyncTask<String> worldController() {undefined
            logger.info(Thread.currentThread().getName() + " 进入helloController方法");

            // 3s钟没返回,则认为超时
            WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, new Callable<String>() {undefined

                @Override
                public String call() throws Exception {undefined
                    logger.info(Thread.currentThread().getName() + " 进入call方法");
                    String say = hello.sayHello();
                    logger.info(Thread.currentThread().getName() + " 从helloService方法返回");
                    return say;
                }
            });
            logger.info(Thread.currentThread().getName() + " 从helloController方法返回");

            webAsyncTask.onCompletion(new Runnable() {undefined

                @Override
                public void run() {undefined
                    logger.info(Thread.currentThread().getName() + " 执行完毕");
                }
            });

            webAsyncTask.onTimeout(new Callable<String>() {undefined

                @Override
                public String call() throws Exception {undefined
                    logger.info(Thread.currentThread().getName() + " onTimeout");
                    // 超时的时候,直接抛异常,让外层统一处理超时异常
                    throw new TimeoutException("调用超时");
                }
            });
            return webAsyncTask;
        }

        /**
         * 异步调用,异常处理,详细的处理流程见MyExceptionHandler类
         *
         * @return
         */
        @GetMapping("/exception")
        public WebAsyncTask<String> exceptionController() {undefined
            logger.info(Thread.currentThread().getName() + " 进入helloController方法");
            Callable<String> callable = new Callable<String>() {undefined

                @Override
                public String call() throws Exception {undefined
                    logger.info(Thread.currentThread().getName() + " 进入call方法");
                    throw new TimeoutException("调用超时!");
                }
            };
            logger.info(Thread.currentThread().getName() + " 从helloController方法返回");
            return new WebAsyncTask<>(20000, callable);
        }
    }

运行结果如下:

2017-12-07 19:10:26.582 INFO 6196 --- [nio-8060-exec-4] c.travelsky.controller.HelloController : http-nio-8060-exec-4 进入helloController方法
2017-12-07 19:10:26.585 INFO 6196 --- [nio-8060-exec-4] c.travelsky.controller.HelloController : http-nio-8060-exec-4 从helloController方法返回
2017-12-07 19:10:26.589 INFO 6196 --- [nio-8060-exec-4] c.t.i.MyAsyncHandlerInterceptor : http-nio-8060-exec-4 进入afterConcurrentHandlingStarted方法
2017-12-07 19:10:26.591 INFO 6196 --- [ MvcAsync2] c.travelsky.controller.HelloController : MvcAsync2 进入call方法
2017-12-07 19:10:26.591 INFO 6196 --- [ MvcAsync2] com.travelsky.service.HelloService : MvcAsync2 进入sayHello方法!
2017-12-07 19:10:28.591 INFO 6196 --- [ MvcAsync2] c.travelsky.controller.HelloController : MvcAsync2 从helloService方法返回
2017-12-07 19:10:28.600 INFO 6196 --- [nio-8060-exec-5] c.t.i.MyAsyncHandlerInterceptor : http-nio-8060-exec-5服务调用完成,返回结果给客户端
2017-12-07 19:10:28.601 INFO 6196 --- [nio-8060-exec-5] c.travelsky.controller.HelloController : http-nio-8060-exec-5 执行完毕

这种方式和上面的callable方式最大的区别就是,WebAsyncTask支持超时,并且还提供了两个回调函数,分别是onCompletion和onTimeout,顾名思义,这两个回调函数分别在执行完成和超时的时候回调。

3、Deferred方式实现异步调用

在我们是生产中,往往会遇到这样的情景,controller中调用的方法很多都是和第三方有关的,例如JMS,定时任务,队列等,拿JMS来说,比如controller里面的服务需要从JMS中拿到返回值,才能给客户端返回,而从JMS拿值这个过程也是异步的,这个时候,我们就可以通过Deferred来实现整个的异步调用。

首先,我们来模拟一个长时间调用的任务,代码如下:

    @Component
    public class LongTimeTask {undefined
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Async
        public void execute(DeferredResult<String> deferred){undefined
            logger.info(Thread.currentThread().getName() + "进入 taskService 的 execute方法");
            try {undefined
                // 模拟长时间任务调用,睡眠2s
                TimeUnit.SECONDS.sleep(2);
                // 2s后给Deferred发送成功消息,告诉Deferred,我这边已经处理完了,可以返回给客户端了
                deferred.setResult("world");
            } catch (InterruptedException e) {undefined
                e.printStackTrace();
            }
        }
    }

接着,我们就来实现异步调用,controller如下:

    @RestController
    public class AsyncDeferredController {undefined
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        private final LongTimeTask taskService;
        @Autowired
        public AsyncDeferredController(LongTimeTask taskService) {undefined
            this.taskService = taskService;
        }

        @GetMapping("/deferred")
        public DeferredResult<String> executeSlowTask() {undefined
            logger.info(Thread.currentThread().getName() + "进入executeSlowTask方法");
            DeferredResult<String> deferredResult = new DeferredResult<>();
            // 调用长时间执行任务
            taskService.execute(deferredResult);
            // 当长时间任务中使用deferred.setResult("world");这个方法时,会从长时间任务中返回,继续controller里面的流程
            logger.info(Thread.currentThread().getName() + "从executeSlowTask方法返回");
            // 超时的回调方法
            deferredResult.onTimeout(new Runnable(){undefined

                @Override
                public void run() {undefined
                    logger.info(Thread.currentThread().getName() + " onTimeout");
                    // 返回超时信息
                    deferredResult.setErrorResult("time out!");
                }
            });

            // 处理完成的回调方法,无论是超时还是处理成功,都会进入这个回调方法
            deferredResult.onCompletion(new Runnable(){undefined

                @Override
                public void run() {undefined
                    logger.info(Thread.currentThread().getName() + " onCompletion");
                }
            });
            return deferredResult;
        }
    }

执行结果如下:

2017-12-07 19:25:40.192 INFO 6196 --- [nio-8060-exec-7] c.t.controller.AsyncDeferredController : http-nio-8060-exec-7进入executeSlowTask方法
2017-12-07 19:25:40.193 INFO 6196 --- [nio-8060-exec-7] .s.a.AnnotationAsyncExecutionInterceptor : No TaskExecutor bean found for async processing
2017-12-07 19:25:40.194 INFO 6196 --- [nio-8060-exec-7] c.t.controller.AsyncDeferredController : http-nio-8060-exec-7从executeSlowTask方法返回
2017-12-07 19:25:40.198 INFO 6196 --- [nio-8060-exec-7] c.t.i.MyAsyncHandlerInterceptor : http-nio-8060-exec-7 进入afterConcurrentHandlingStarted方法
2017-12-07 19:25:40.202 INFO 6196 --- [cTaskExecutor-1] com.travelsky.controller.LongTimeTask : SimpleAsyncTaskExecutor-1进入 taskService 的 execute方法
2017-12-07 19:25:42.212 INFO 6196 --- [nio-8060-exec-8] c.t.i.MyAsyncHandlerInterceptor : http-nio-8060-exec-8服务调用完成,返回结果给客户端
2017-12-07 19:25:42.213 INFO 6196 --- [nio-8060-exec-8] c.t.controller.AsyncDeferredController : http-nio-8060-exec-8 onCompletion

从上面的执行结果不难看出,容器线程会立刻返回,应用程序使用线程池里面的cTaskExecutor-1线程来完成长时间任务的调用,当调用完成后,容器又启了一个连接线程,来返回最终的执行结果。

这种异步调用,在容器线程资源非常宝贵的时候,能够大大的提高整个系统的吞吐量。

ps:异步调用可以使用AsyncHandlerInterceptor进行拦截,使用示例如下:

    @Component
    public class MyAsyncHandlerInterceptor implements AsyncHandlerInterceptor {undefined
        private static final Logger logger = LoggerFactory.getLogger(MyAsyncHandlerInterceptor.class);
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {undefined
            return true;
        }

        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                ModelAndView modelAndView) throws Exception {undefined
    //        HandlerMethod handlerMethod = (HandlerMethod) handler;
            logger.info(Thread.currentThread().getName()+ "服务调用完成,返回结果给客户端");
        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {undefined
            if(null != ex){undefined
                System.out.println("发生异常:"+ex.getMessage());
            }
        }

        @Override
        public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {undefined
            // 拦截之后,重新写回数据,将原来的hello world换成如下字符串
            String resp = "my name is chhliu!";
            response.setContentLength(resp.length());
            response.getOutputStream().write(resp.getBytes());
            logger.info(Thread.currentThread().getName() + " 进入afterConcurrentHandlingStarted方法");
        }
    }

有兴趣的可以了解下,我这里的主题是异步调用,其他的相关知识点,以后在讲解吧。希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot异步处理的四种实现方式

    本篇文章我们以SpringBoot中异步的使用(包括:异步调用和异步方法两个维度)来进行讲解. 异步请求与同步请求 我们先通过一张图来区分一下异步请求和同步请求的区别: 在上图中有三个角色:客户端.Web容器和业务处理线程. 两个流程中客户端对Web容器的请求,都是同步的.因为它们在请求客户端时都处于阻塞等待状态,并没有进行异步处理. 在Web容器部分,第一个流程采用同步请求,第二个流程采用异步回调的形式. 通过异步处理,可以先释放容器分配给请求的线程与相关资源,减轻系统负担,从而增加了服务器对

  • 深入理解spring boot异步调用方式@Async

    本文主要给大家介绍了关于spring boot异步调用方式@Async的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 1.使用背景 在日常开发的项目中,当访问其他人的接口较慢或者做耗时任务时,不想程序一直卡在耗时任务上,想程序能够并行执行,我们可以使用多线程来并行的处理任务,也可以使用spring提供的异步处理方式@Async. 2.异步处理方式 调用之后,不返回任何数据. 调用之后,返回数据,通过Future来获取返回数据 3.@Async不返回数据 使用@EnableAsyn

  • Springboot集成定时器和多线程异步处理操作

    需求:用@schedule标签进行定时处理逻辑,由于业务处理速度慢,需要每次执行逻辑放在不同的线程里异步执行 springboot集成多线程异步,直接上配置: /** * 线程池异步配置 */ @Configuration @EnableAsync public class ThreadExecutorConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskE

  • 详解Spring Boot 异步执行方法

    最近遇到一个需求,就是当服务器接到请求并不需要任务执行完成才返回结果,可以立即返回结果,让任务异步的去执行.开始考虑是直接启一个新的线程去执行任务或者把任务提交到一个线程池去执行,这两种方法都是可以的.但是 Spring 这么强大,肯定有什么更简单的方法,就 google 了一下,还真有呢.就是使用 @EnableAsync 和 @Async 这两个注解就 ok 了. 给方法加上 @Async 注解 package me.deweixu.aysncdemo.service; public int

  • 详解SpringBoot中异步请求和异步调用(看完这一篇就够了)

    一.SpringBoot中异步请求的使用 1.异步请求与同步请求 特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应.一句话:增加了服务器对客户端请求的吞吐量(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲). 2.异步请求的实现 方式一:Servlet方式实现异步请求

  • SpringBoot多线程进行异步请求的处理方式

    目录 SpringBoot多线程进行异步请求的处理 第一步:编写配置类 第二步:对方法使用注解标注为使用多线程进行处理 SpringBoot请求线程优化 使用Callable来实现 1.异步调用的另一种方式 3.Deferred方式实现异步调用 SpringBoot多线程进行异步请求的处理 近期在协会博客园中,有人发布了博客,系统进行查重的时候由于机器最低配置进行大量计算时需要十秒左右时间才能处理完,由于一开始是单例模式,导致,在某人查重的时候整个系统是不会再响应别的请求的,导致了系统假死状态,

  • 实现PHP多线程异步请求的3种方法

    在网上看过很多版本的PHP异步请求方法,这里简单总结几个常用方法分享给大家 1.用CURL实现一步请求 CURL扩展是我们在开发过程中最常用的一种方法,他是一个强大的HTTP命令行工具,可以模拟POST/GET等HTTP请求,然后得到和提取数据,显示在"标准输出"(stdout)上面. 示例: 复制代码 代码如下: <?php $cl = curl_init(); $curl_opt = array(CURLOPT_URL, 'http://www.uncletoo.com/de

  • springboot实现拦截器的3种方式及异步执行的思考

    目录 springboot 拦截器 springboot 入门案例 maven 引入 启动类 定义 Controller 拦截器定义 基于 Aspect 基于 HandlerInterceptor 基于 ResponseBodyAdvice 测试 异步执行 定义异步线程池 异步执行的 Controller 思考 测试 反思 springboot 拦截器 实际项目中,我们经常需要输出请求参数,响应结果,方法耗时,统一的权限校验等. 本文首先为大家介绍 HTTP 请求中三种常见的拦截实现,并且比较一

  • Spring中注解方式的异步请求

    一.Servlet3.0异步请求 @WebServlet(value = "/async", asyncSupported = true) public class HelloAsyncServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //

  • SpringBoot@Aspect 打印访问请求和返回数据方式

    目录 SpringBoot@Aspect 打印访问请求和返回数据 aspect:第一种方式 aspect:第二种方式 SpringBoot @Aspect注解详情 1.添加maven依赖注解 2.添加AOP类 3.设置切面点 4.配置前置通知 5.配置后置返回通知 6.后置异常通知 7.后置最终通知 8.环绕通知 SpringBoot@Aspect 打印访问请求和返回数据 为什么要用aspect, 使用aspect 可以使记录日志的功能面向切面,这样可以降低代码的耦合性.提供了两种方式对输入输出

  • springboot使用log4j2异步日志提升性能的实现方式

    目录 一.引入disruptor 二. 全局异步模式 三.异步/同步混合模式 同步日志的业务流程处理和日志打印是在同一个线程,日志打印的过程实际上是写文件IO的过程,这个过程是相对耗时的,并且会阻塞主线程的执行,只有日志打印完成后才会继续执行业务处理代码.如果日志量比较大,会影响主业务流程的处理效率.异步日志实现方式:将日志存入一个单独的队列中,有一个单独的线程从队列中获取日志并写入磁盘文件. 日志放入队列的耗时,肯定比磁盘写IO文件耗时要少的多得多,所以对主业务流程影响极小. 一个单独的线程进

  • vue异步请求数据重新渲染方式

    目录 vue异步请求数据重新渲染 下面介绍一种方法解决 自定义组件异步获取数据重新渲染 视图层 逻辑层 vue异步请求数据重新渲染 vue异步请求数据时往往不能及时更新, 下面介绍一种方法解决 export default {         name: "pic",         created() {            this.getList();         },         data(){             return{num:[]}         }

  • Spring Boot实现异步请求(Servlet 3.0)

    在spring 3.2 及以后版本中增加了对请求的异步处理,旨在提高请求的处理速度降低服务性能消耗. 在我们的请求中做了耗时处理,当并发请求的情况下,为了避免web server的连接池被长期占用而引起性能问题,调用后生成一个非web的服务线程来处理,增加web服务器的吞吐量. 为此 Servlet 3.0 新增了请求的异步处理,Spring 也在此基础上做了封装处理. 本文还是以代码例子的方式说明如何在 Spring Boot 中应用异步请求. 首先说一下几个要点: 1.@WebFilter

  • javascript异步编程的六种方式总结

    异步编程 众所周知 JavaScript 是单线程工作,也就是只有一个脚本执行完成后才能执行下一个脚本,两个脚本不能同时执行,如果某个脚本耗时很长,后面的脚本都必须排队等着,会拖延整个程序的执行.那么如何让程序像人类一样可以多线程工作呢?以下为几种异步编程方式的总结,希望与君共勉. 回调函数 事件监听 发布订阅模式 Promise Generator (ES6) async (ES7) 异步编程传统的解决方案:回调函数和事件监听 初始示例:假设有两个函数, f1 和 f2,f1 是一个需要一定时

随机推荐