php框架

php框架的功能通用的路由,autoload。服务端mysql封装,日志组件。前端的页面渲染(smarty封装个)。在工作时听说别的部门换框架性能提升,所以调研了下常见的框架,包含ci,laravel,yii,yaf,会介绍下功能,另外给出号称最快框架yaf和常用yii和裸写框架的性能差。另外想实现rpc并发,http一般用过multi_curl可以,公司用的thrift没有实现并发,所以研究了下php协程,curl_multi,swoole异步,rpc中并发实现,corotine等。 常用php框架提供功能ci http://codeigniter.org.cn/use...Route.autoload.载入文件正常controller 中load->helper(xx)。直接用xxlog. db. hook.公共函数。代码逻辑分层。ui抽象。模板。laravel https://laravel-china.org/doc...路由中间件(前置后置)配置区分环境,本地和线上密码不放其中数据库 创建表,编辑,删除,迁移,回滚,软删除和恢复(标记删除位)。ORM链式操作依赖注入,依赖自动发现。 IOC 平时的if new 这种工厂模式,IoC模式看作工厂模式的升华,以前在工厂模式里写死了的对象,IoC模式 改为配置XML文件,这就把工厂和要生成的对象两者隔离类(DatabaseQueue,queue,QueueContract),serviceprovider(外部调这个)=>bind(将类绑定到容器)。调用Queue::xx。依赖注入可以直接调用$类->method。通过门面可以类::method【https://www.cnblogs.com/shiwenhu/p/6882340.html】事件 事件映射protected $listen = [ 'App\Events\OrderShipped' => [ 'App\Listeners\SendShipmentNotification', ],];写事件OrderShipped,写监听SendShipmentNotification 可以继承队列分发事件:public function ship($orderId) { order=Order::findOrFail(orderId); // 订单的发货逻辑... event(new OrderShipped($order)); }队列 ,redis,db, 广播js监听任务调度 只是cron yii https://www.yiichina.com/doc/... 功能全面读介于ci和laravel之间,前端支持功能丰富。组件和行为是它的特色行为 要定义行为,通过继承 yii\base\Behavior 。覆盖其中的events方法,public function events() { return [ ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', ]; } public function beforeValidate($event) { // 处理器方法逻辑 },附加行为:lass User extends ActiveRecord { public function behaviors() { return [ [ 'class' => MyBehavior::className(), 'prop1' => 'value1', 'prop2' => 'value2', ]]}}或者attach组件就可以使用行为了。直观感受下 ...

May 15, 2019 · 3 min · jiezi

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

<font color=gray> 原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10661591.html,否则将追究法律责任!!! </font>一、SpringBoot中异步请求的使用1、异步请求与同步请求特点:可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。<font color=red>一句话:增加了服务器对客户端请求的吞吐量</font>(实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲)。2、异步请求的实现方式一:Servlet方式实现异步请求 @RequestMapping(value = “/email/servletReq”, method = GET) public void servletReq (HttpServletRequest request, HttpServletResponse response) { AsyncContext asyncContext = request.startAsync(); //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理 asyncContext.addListener(new AsyncListener() { @Override public void onTimeout(AsyncEvent event) throws IOException { System.out.println(“超时了…”); //做一些超时后的相关操作… } @Override public void onStartAsync(AsyncEvent event) throws IOException { System.out.println(“线程开始”); } @Override public void onError(AsyncEvent event) throws IOException { System.out.println(“发生错误:"+event.getThrowable()); } @Override public void onComplete(AsyncEvent event) throws IOException { System.out.println(“执行完成”); //这里可以做一些清理资源的操作… } }); //设置超时时间 asyncContext.setTimeout(20000); asyncContext.start(new Runnable() { @Override public void run() { try { Thread.sleep(10000); System.out.println(“内部线程:” + Thread.currentThread().getName()); asyncContext.getResponse().setCharacterEncoding(“utf-8”); asyncContext.getResponse().setContentType(“text/html;charset=UTF-8”); asyncContext.getResponse().getWriter().println(“这是异步的请求返回”); } catch (Exception e) { System.out.println(“异常:"+e); } //异步请求完成通知 //此时整个请求才完成 asyncContext.complete(); } }); //此时之类 request的线程连接已经释放了 System.out.println(“主线程:” + Thread.currentThread().getName()); }方式二:使用很简单,直接返回的参数包裹一层callable即可,可以继承WebMvcConfigurerAdapter类来设置默认线程池和超时处理 @RequestMapping(value = “/email/callableReq”, method = GET) @ResponseBody public Callable<String> callableReq () { System.out.println(“外部线程:” + Thread.currentThread().getName()); return new Callable<String>() { @Override public String call() throws Exception { Thread.sleep(10000); System.out.println(“内部线程:” + Thread.currentThread().getName()); return “callable!”; } }; } @Configuration public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter { @Resource private ThreadPoolTaskExecutor myThreadPoolTaskExecutor; @Override public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { //处理 callable超时 configurer.setDefaultTimeout(601000); configurer.setTaskExecutor(myThreadPoolTaskExecutor); configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor()); } @Bean public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() { return new TimeoutCallableProcessingInterceptor(); }}方式三:和方式二差不多,在Callable外包一层,给WebAsyncTask设置一个超时回调,即可实现超时处理 @RequestMapping(value = “/email/webAsyncReq”, method = GET) @ResponseBody public WebAsyncTask<String> webAsyncReq () { System.out.println(“外部线程:” + Thread.currentThread().getName()); Callable<String> result = () -> { System.out.println(“内部线程开始:” + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(4); } catch (Exception e) { // TODO: handle exception } logger.info(“副线程返回”); System.out.println(“内部线程返回:” + Thread.currentThread().getName()); return “success”; }; WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result); wat.onTimeout(new Callable<String>() { @Override public String call() throws Exception { // TODO Auto-generated method stub return “超时”; } }); return wat; }方式四:DeferredResult可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。@RequestMapping(value = “/email/deferredResultReq”, method = GET) @ResponseBody public DeferredResult<String> deferredResultReq () { System.out.println(“外部线程:” + Thread.currentThread().getName()); //设置超时时间 DeferredResult<String> result = new DeferredResult<String>(601000L); //处理超时事件 采用委托机制 result.onTimeout(new Runnable() { @Override public void run() { System.out.println(“DeferredResult超时”); result.setResult(“超时了!”); } }); result.onCompletion(new Runnable() { @Override public void run() { //完成后 System.out.println(“调用完成”); } }); myThreadPoolTaskExecutor.execute(new Runnable() { @Override public void run() { //处理业务逻辑 System.out.println(“内部线程:” + Thread.currentThread().getName()); //返回结果 result.setResult(“DeferredResult!!”); } }); return result; }二、SpringBoot中异步调用的使用1、介绍异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。2、使用方式(基于spring下)需要在启动类加入@EnableAsync使异步调用@Async注解生效在需要异步执行的方法上加入此注解即可@Async(“threadPool”),threadPool为自定义线程池代码略。。。就俩标签,自己试一把就可以了3、注意事项在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。所以最好我们来自定义一个线程池。调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。下面会重点讲述。。4、什么情况下会导致@Async异步方法会失效?<font color=red>调用同一个类下注有@Async异步方法</font>:在spring中像@Async和@Transactional、cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。<font color=red>调用的是静态(static )方法</font><font color=red>调用(private)私有化方法</font>5、解决4中问题1的方式(其它2,3两个问题自己注意下就可以了)<font color=red>将要异步执行的方法单独抽取成一个类</font>,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了。其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。那么我们就可以<font color=red>通过上下文获取自己的代理对象调用异步方法</font>。@Controller@RequestMapping("/app”)public class EmailController { //获取ApplicationContext对象方式有多种,这种最简单,其它的大家自行了解一下 @Autowired private ApplicationContext applicationContext; @RequestMapping(value = “/email/asyncCall”, method = GET) @ResponseBody public Map<String, Object> asyncCall () { Map<String, Object> resMap = new HashMap<String, Object>(); try{ //这样调用同类下的异步方法是不起作用的 //this.testAsyncTask(); //通过上下文获取自己的代理对象调用异步方法 EmailController emailController = (EmailController)applicationContext.getBean(EmailController.class); emailController.testAsyncTask(); resMap.put(“code”,200); }catch (Exception e) { resMap.put(“code”,400); logger.error(“error!",e); } return resMap; } //注意一定是public,且是非static方法 @Async public void testAsyncTask() throws InterruptedException { Thread.sleep(10000); System.out.println(“异步任务执行完成!”); }}<font color=red>开启cglib代理,手动获取Spring代理类</font>,从而调用同类下的异步方法。首先,在启动类上加上<font color=red>@EnableAspectJAutoProxy(exposeProxy = true)</font>注解。代码实现,如下:@Service@Transactional(value = “transactionManager”, readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)public class EmailService {@Autowiredprivate ApplicationContext applicationContext;@Asyncpublic void testSyncTask() throws InterruptedException { Thread.sleep(10000); System.out.println(“异步任务执行完成!”);}public void asyncCallTwo() throws InterruptedException { //this.testSyncTask();// EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);// emailService.testSyncTask(); boolean isAop = AopUtils.isAopProxy(EmailController.class);//是否是代理对象; boolean isCglib = AopUtils.isCglibProxy(EmailController.class); //是否是CGLIB方式的代理对象; boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class); //是否是JDK动态代理方式的代理对象; //以下才是重点!!! EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class); EmailService proxy = (EmailService) AopContext.currentProxy(); System.out.println(emailService == proxy ? true : false); proxy.testSyncTask(); System.out.println(“end!!!”);}}三、异步请求与异步调用的区别两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。异步请求是会一直等待response相应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。四、总结异步请求和异步调用的使用到这里基本就差不多了,有问题还希望大家多多指出。这边文章提到了动态代理,而spring中Aop的实现原理就是动态代理,后续会对动态代理做详细解读,还望多多支持哈~个人博客地址:csdn:https://blog.csdn.net/tiantuo6513 cnblogs:https://www.cnblogs.com/baixianlongsegmentfault:https://segmentfault.com/u/baixianlong github:https://github.com/xianlongbai ...

April 8, 2019 · 3 min · jiezi

热点账户问题和常用解决方案【中】

话接上回,上篇阐述了什么是热点账户,基本财务账户如何设计,幂等健和链式设计!本篇将针对热点账户在实践中引发的问题,梳理和拆解业务流,分析问题点,提出七种常用解决方案。一、性能问题初现上线初期数据量较小,运行正常!一次大促后,账户流水的总数目接近亿级别,初现性能问题:系统整体的qps也就10+,但热点账户写入失败率偏高,并且随数据量增加失败率逐步升高;整个账户系统全靠上游有redo标识位不断重试,才能保证最终写入成功!哈哈,作为一名拥有三年工作经验的老码农,面对问题,要做的第一件事,就是静,抽根烟静静,准备开搞!二、数据流拆解拿到问题,抽根烟静一下之后,分析问题需要三步:梳理数据流,拆解过程,定位问题点。先对财务账户更新的数据流进行拆解链式锁后的基本账户操作过程,分为如下五阶段请求阶段:账户操作请求。查询阶段:查询当前账户信息。主要获取当前链,资金数据等!计算阶段:对链和操作的资金进行计算,判定资金操作合规,同时保证幂等和并发!写入阶段:事务同步写入流水和余额响应阶段:告知上游回调三、链路分析梳理数据流后,接下来分析每个阶段可能引发的问题。按照优先级,先分析业务问题区域(读取阶段,计算阶段,写入阶段),通常问题会出现在业务阶段;之后,再分析框架问题区域(请求阶段和回调阶段),该区域出现问题的情况偏小,但是一旦出现问题,就是比较有意思^^!3.1 业务问题区域分析读取阶段,计算阶段,写入阶段三个阶段是具体的业务操作,从并发和耗时两个角度来分析下可能的问题点3.2.1 耗时分析耗时分为三块查询耗时:RDS拥有亿级别数据量,查询未中primary,但命中索引,业务数据体并未完全在索引中,因此访问数据走index match;数据主键聚簇,唯一健索引查询获取数据,page极难命中cache,也不会命中磁盘电梯算法优化!结合实际情况,查询耗时在10-100ms级别写入耗时:insert 包含了自增,理论上在数据落盘是追加写,即使uniq_key去创建索引的情况下,耗时在ms级过程耗时:长连接情况下,init conn时间基本可以忽略,但是读写两次往返数据库的链路时间还是需要考虑,整体预估在1-2ms之间从整体上看,预估该阶段的耗时在10-100+ms,从实际失败率来看也基本一致!3.2.2 并发分析天级QPS:当时分析天级几十万单,天级QPS不到10,不高!瞬间QPS:每个订单拆解到资金流后,会同时操作多次热点账户,瞬间qps相对很高,理论qps就可能达到语言上限,由于上游链路限流1024,按照10级别操作拆分,理论上满池QPS在万级别。考虑实际单量,瞬间QPS=单量(10)*拆解量(10),实际的满额预估QPS可能到100+ !按照上面分析,在瞬时QPS达到10+的情况下,热点账户整体延时在10-100+ms,由于DB在写入uniq_key保证链点唯一,所以出现并发写入失败也在情理之中;并且随着数据量的提升,读取延时增加,写入失败率会继续增加。3.2 框架问题区域请求阶段做为入口,一般也分为三个小阶段webserver接收请求框架加载和路由基础校验请求阶段核心耗时一般存在于框架加载和路由,高并发场景webserver和upstream之间的调用也是一个可能出问题点!当时财务系统,采用欢总封装的go-thrift,并且其他模块并未出现请求阶段问题,所以并未对这个阶段的latency和并发做一个衡量,重点分析了业务模块!四、解决方案4.1 读取和写入阶段优化通过上面分析,目前问题的痛点是并发读取热点账户数据高延时引发写入失败,提升读性能成为了关键读性能提升有两个基本思路:读的时效快和读的次数少针对上面两个基本思路,结合财务账户情况提出了五种提升读性能的解决方案【读快】持久化last record:不从全量数据里面读,抽离子账户的最新信息,持久化到单独的表中或者内存中,降低整体数据量,提升了读性能。缺点是要保证持久化信息的准确性,引入事务写。【读快】纵向切分-时间分库分表:按照时间进行纵向切分,降低查询范围内的数据量,提升读性能。缺点是跨时间读不友好,开发量也不小【读快】纵向切分-归档:历史数据归档是实现相对简单,跨时间读也比较友好,随着数据量的提升,也是必须要做,之后会详细介绍归档方案和选型。【读快】横向切分-业务分库分表:按照账户类型或者城市分库分表,可以优化读写数据量,同时,跨表读负担也会较小。但对于热点账户或者热点城市,依然聚簇,效果不是很明显。同时,再次对热点账户进行横向分库分表也是极度不推荐,引入的极高的读写成本均。【读少】阶段快照:一定量或者一定时间内的数据,持久化一次。优势是极大的降低读写次数;缺点是需要复杂的逻辑来保证undo操作和数据一致性!五种解决方案各有千秋,作为一个初期的财务系统推荐采用持久化last record和数据归档来保证写入读性能和整体读的数据量。如果系统发展到了中期,推荐按照时间分库分表。如果发展到了双11或者春晚某些极端场景,牺牲掉部分准确性,采用阶段快照也是可以的。4.2 计算阶段优化存在计算阶段造成的最大影响也就是引起了两次数据传输,通常是不可避免的,但是如果真的是要进行提升有一种方案通用方案DB计算:通过存储计算,转嫁计算成本给DB,减少一次链路请求。但不太推荐,复杂的sql往往有坑,insert computer from select 还会造成大面积的数据隔离,很容易引起死锁。4.3 请求和回调阶段优化请求阶段一般有三种形式:同步调用,异步调用和伪同步调用!前两种调用非常常见:同步爆池的情况,一般采用限流来降压,采用漏桶,令牌桶等等策略;异步调用通常采用消息队列来削峰填谷;这里重点阐述对于支付和财务系统在请求阶段经常采用的伪同步的方式伪同步流量较早出现在innodb,leveldb等存储引擎为了利用追加写提升写入性能,采用类WAL日志来持久化数据。通常伪同步方案采用三件套:WAL日志+校验位+广播消息来完成一次完整的请求!流程图一般如下请求阶段:同步请求调用,核心要素追加写入wal日志,变更校验位,完成同步调用!此处追加写保证了快速写入,校验位来保证数据的最终写入成功。图中1,2异步阶段:通过读取wal日志的核心数据,进行复杂事务处理,如果成功进入下一阶段;如果失败,没问题,通过外部trigger来触发redo操作!如果多次redo依然失败,那么通过undo来回滚数据。回调阶段:如果成功,更改校验位,同时发布成功广播消息,关注结果和时效性的模块,可以获取最终成功的标识!如果undo回滚数据,则发布失败广播消息,告知结果失败!在伪同步的模式下指标衡量:QPS:伪同步模式,采用WAL核心要素追加写,所以写性能可以极大提升,进而满额QPS相对直接同步调用也大量提升时效性:伪同步并非完全同步,所以结果需要监听回调。对于结果强一致的请求,必须监听回调,确保一致,时效性降低;对于弱一致可以认为同步回调即成功,时效性提升。失败率:操作知识核心要素追加写入,真正的操作通过异步保证,整体成功率提升!对于资金处理过程,大量采用伪同步的请求方式来保证快速写入和最终一致性。4.4 解决方案总结总的来说,归结了七种优化方式(哈哈,上篇写的八种优化,当时总结的,现在愣是想不到还有啥了^^)。其中请求和回调的伪同步方式,是在架构层面优化,这个在多数的财务系统和财务系统的内部数据流中都会用到;而读写和计算阶段的优化,可以跟进实际业务情况进行选型处理。五、事故复盘面对各种优化方案,需要结合实际情况做出取舍,有的是长期方案,有的是快速方案,但一定需要想清楚了再开搞,过程中有一个对小拽之后影响很大的事故,引以为戒。翻车过程:当时觉的读->计算->写这个过程,两次读DB操作,下沉计算过程到DB后,通过DB计算,可以减少一次数据库请求。于是合并了一个大SQL,也就是所谓的insert ( field computer from select),觉的写了个狂赚酷炫吊炸天的SQL,一上线,库锁死了!幸好有前置的redo flag,全量redo数据恢复,要不然估计直接祭天了!对于这个复杂大SQL事故,小拽总结了三个方面莫炫技:没啥好说的,解决问题的成就感要远大于炫技!简单设计:简单的设计通常意味着可依赖;复杂的设计要尽可能的拆解,想清楚,队友都理解不了的设计,那就别上线了,可能真的需要再思考拆解下尊重线上:核心服务基本上线流程一定要遵守,测试,监控和回滚方案缺一不可六、小结本篇主要针对热点账户问题提出了七种常用的解决方案,下篇将继续引申探索下,各种解决方案在不规则高并发场景,例如双十一,微博热点事件中如何套用预知后事如何,下回再聊!【转载请注明:热点账户问题和常用解决方案【中】 | 靠谱崔小拽 】

April 1, 2019 · 1 min · jiezi

Vuex 的异步数据更新(小记)

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数mutation 是同步执行,不是异步执行。由于Mutations不接受异步函数,要通过Actions来实现异步请求。export const setAddPurchaseStyle = ({commit, state}, obj) => { url=‘http://xxx.com’ + ‘/json/’ + ‘/development’ + ‘/purchaserexp/create_company.js’; let _tempObj={}; // 着键是这个 return return new Promise(function(resolve, reject) { axios.get(url, {}).then((response) => { resolve(‘请求成功后,传递到 then’); }, (response) => { //失败 console.info(’error’, response); reject(‘请求失败后,传递到 catch’) }); }).then((res)=>{ // res 是 (请求成功后,传递到 then) commit(‘putSSS’, Math.random()); // 在Promise的成功中,调用 mutations 的方法 })};在Mutations中通过putSSS接收并更新style数据:export const putSSS=(state, val) => { state.style = val;};组件创建时更新数据,写在 created 中this.$store.dispatch(‘setStyle’,{ id:this.id, name: this.name, });使用 mapActions使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store)methods: { …mapActions([ ‘setStyle’ ]), test(){ // 映射后可直接使用方法,不需要写 this.$store.dispatch this.setStyle({ id:this.id, name: this.name, }); }} ...

March 6, 2019 · 1 min · jiezi

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

点我看看~话不多说,上源码:// 核心方法!核心方法!核心方法!也是最难懂的!—-STARTconst resolvePromise = (promise2, x, resolve, reject) => { if (promise2 === x) { return reject(new TypeError(“A promise cannot be resolved with itself”)) } if (x && (typeof x === “object” || typeof x === “function”)) { try { let then = x.then; if (typeof then === “function”) { then.call(x, y => { resolve(y); resolvePromise(promise2, y, resolve, reject); }, r => { reject(r); }) } else { resolve(x); } } catch (e) { reject(e) } } else { resolve(x) }};// 核心方法!核心方法!核心方法!也是最难懂的!—-END// Commitment就是class Commitment { constructor (executor) { this.status = “PENDING” this.value = void(0); this.reason = void(0); this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.status === “PENDING”) { this.status = “RESOLVED”; this.value = value; this.onResolvedCallbacks.forEach(cb => cb()) } } const reject = (reason) => { if (this.status === “PENDING”) { this.status = “REJECTED”; this.reason = reason; this.onRejectedCallbacks.forEach(cb => cb()) } } try { executor(resolve, reject) } catch (e) { reject(e) } } then (onResolvedFn, onRejectedFn) { let promise2, x; promise2 = new Promise((resolve, reject) => { if (this.status === “RESOLVED”) { setTimeout(() => { try { x = onResolvedFn(this.value); resolvePromise(promise2, x, resolve, reject); } catch(e) { reject(e) } }, 0) } if (this.status === “REJECTED”) { setTimeout(() => { try { x = onRejectedFn(this.reason); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } if (this.status === “PENDING”) { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { x = onResolvedFn(this.value); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { x = onRejectedFn(this.reason); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }); } }); return promise2 } catch (onRejectedFn) { return this.then(null, onRejectedFn) } // 静态方法 static resolve (value) { return new Commitment((resolve, reject) => { resolve(value) }) } static reject (err) { return new Commitment((resolve, reject) => { reject(err) }) } static all () { let result = []; return new Commitment((resolve, reject) => { if (arguments.length === 0) { return reject(new TypeError(“undefined is not iterable (cannot read property Symbol(Symbol.iterator))”)) } else { let args = […arguments][0], result = []; let count = args.length, num = 0; for (let i = 0; i < count; i++) { let item = args[i]; try { let then = item.then; if (typeof then === “function”) { then.call(item, y => { num += 1; result[i] = y; if (num === count) { resolve(result) } }, r => { reject(r) }) } else { result[i] = item; if (num === count) { resolve(result) } } } catch (e) { reject(e) } } } }) } static race () { // 未完待续 }} ...

February 28, 2019 · 3 min · jiezi