概述

异步编程离不开promise, async, 事件响应这些东西,为了更好地异步编程,我打算探究一下promise的实现原理,方法是自己实现一个简易的promise。

根据promise mdn上的描述,我们主要实现如下api:

  1. Promise.prototype.resolve
  2. Promise.prototype.reject
  3. Promise.prototype.then
  4. Promise.all
  5. Promise.race

为了更好地性能和使用,我还需要加上惰性求值特性,即:只有调用then的时候才真正调用Promise里面的异步方法。并且我们忽略pending,fulfilled和rejected这几个状态,因为封装后的promise并没有暴露这几个状态,目前看来也没什么用(除非用事件响应实现Promise)。

为了简便,暂时不考虑错误处理

实现resolve,reject和then

其实就是调用resolve和reject的时候调用相应的success和error函数就行,代码如下:

  1. let Promise = function(func) {
  2. this.func = func;
  3. this.success = null;
  4. this.error = null;
  5. }
  6. Promise.prototype.resolve = function(value, that) {
  7. console.log('resolve');
  8. if(typeof that.success == 'function') {
  9. that.success(value);
  10. }
  11. }
  12. Promise.prototype.reject = function(value, that) {
  13. console.log('reject');
  14. if(typeof that.error == 'function') {
  15. that.error(value);
  16. }
  17. }
  18. Promise.prototype.then = function(onFulfilled, onRejected) {
  19. this.success = onFulfilled;
  20. this.error = onRejected;
  21. setTimeout(() => {
  22. this.func(this.resolve, this.reject);
  23. });
  24. }
  25. let myPromise = new Promise(function(resolve, reject){
  26. setTimeout(() => {
  27. resolve("成功!", this);
  28. }, 1000);
  29. });
  30. myPromise.then((successMessage) => {
  31. console.log('输出', successMessage);
  32. });

需要注意的是,这里如果不带入this的话,resolve里面的this就会丢失。

但是这么写不优雅,我想了很多办法,比如重新包装一下,比如用事件响应,但还是解决不了,最后我突然想到,用bind,哇,成功解决。代码如下:

  1. let Promise = function(func) {
  2. this.func = func;
  3. this.success = null;
  4. this.error = null;
  5. }
  6. Promise.prototype.resolve = function(value) {
  7. if(typeof this.success == 'function') {
  8. this.success(value);
  9. }
  10. }
  11. Promise.prototype.reject = function(value) {
  12. if(typeof this.error == 'function') {
  13. this.error(value);
  14. }
  15. }
  16. Promise.prototype.then = function(onFulfilled, onRejected) {
  17. this.success = onFulfilled;
  18. this.error = onRejected;
  19. setTimeout(() => {
  20. this.func(this.resolve.bind(this), this.reject.bind(this));
  21. });
  22. }
  23. let myPromise = new Promise(function(resolve, reject){
  24. setTimeout(() => {
  25. resolve("成功!", this);
  26. }, 1000);
  27. });
  28. myPromise.then((successMessage) => {
  29. console.log('输出', successMessage);
  30. });

值得一提的是,为了实现惰性求值,需要先把异步方法缓存起来,等调用then的时候再调用它。

还有,在Promise内部,为了简便,我使用的是setTimeout进行异步,并没有使用setImmediate

实现all和race

all和race在多异步promise里面非常有用,下面我们来实现它们:

  1. Promise.all = function(promiseArr) {
  2. let results = [];
  3. let sum = promiseArr.length;
  4. let count = 0;
  5. return new Promise(function(resolve, reject) {
  6. if(promiseArr || sum) {
  7. for(let i=0; i<sum; i++) {
  8. promiseArr[i].then((res) => {
  9. results[i] = res;
  10. count ++;
  11. if(count >= sum) {
  12. resolve(results);
  13. }
  14. });
  15. }
  16. }
  17. });
  18. };
  19. Promise.race = function(promiseArr) {
  20. let sum = promiseArr.length;
  21. let count = 0;
  22. return new Promise(function(resolve, reject) {
  23. if(promiseArr || sum) {
  24. for(let i=0; i<sum; i++) {
  25. promiseArr[i].then((res) => {
  26. if(count == 0) {
  27. count ++;
  28. resolve(res);
  29. }
  30. });
  31. }
  32. }
  33. });
  34. };

可以看到,方法是使用传说中的哨兵变量,真的很有用。

测试

简易的测试代码如下:

  1. let myPromise1 = new Promise(function(resolve, reject){
  2. setTimeout(() => {
  3. resolve("成功1111111!");
  4. }, 1000);
  5. });
  6. let myPromise2 = new Promise(function(resolve, reject){
  7. setTimeout(() => {
  8. resolve("成功222222222222!");
  9. }, 1500);
  10. });
  11. myPromise1.then((successMessage) => {
  12. console.log('输出', successMessage);
  13. });
  14. console.time('all计时开始');
  15. Promise.all([myPromise1, myPromise2]).then((results) => {
  16. results.map(item => {
  17. console.log('输出', item);
  18. });
  19. console.timeEnd('all计时开始');
  20. });
  21. console.time('race计时开始');
  22. Promise.race([myPromise1, myPromise2]).then((res) => {
  23. console.log('输出', res);
  24. console.timeEnd('race计时开始');
  25. });

可以看到,all计时刚好1.5秒,race计时刚好1秒。

我学到了什么

  1. 对Promise的理解更加深入。
  2. 对bind的使用更加熟练。
  3. 可以看到,Promise的缺点是对于每个异步方法,都需要用构造函数封装一遍,如果有其它需求,则需要更特别的封装。
  4. 打算找个时间用事件响应重新实现一遍,目前代码有点乱,如果用事件响应的话可能会更加优雅。

实现简易Promise的更多相关文章

  1. 简易promise

    <!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8" ...

  2. 简易promise的实现(二)

    code 上一章中我们遇到了两个问题 1.异步调用顺序的问题 2.then返回一个promise的问题 思考 如果控制异步回调的顺序? 因为异步操的时间作我们无法控制,但是我们只需要按顺序执行回调函数 ...

  3. 简易promise的实现(一)

    code 最近在思考promise的实现原理 于是准备自己写一个简单的demo 一开始想到的问题有两个 1.链式调用 2.异步顺序执行 -------------------------------- ...

  4. ES6之promise原理

    我在这里介绍了promise的原理: https://juejin.im/post/5cc54877f265da03b8585902 我在这里 仅仅张贴 我自己实现的简易promise——DiProm ...

  5. 用JavaScript完成页面自动操作

    在之前的一篇<JavaScript实现按键精灵>中曾记录了几个事件对象,本文将会对它们进行一次实战,要完成的动作包括滚动.点击和翻页. 一.滚动 滚动是通过修改容器元素的scrollTop ...

  6. Promise实现简易AMD加载器

    在最新的Chrome和FF中已经 实现了Promise.有了Promise我们用数行代码即可实现一个简易AMD模式的加载器 var registry = { promises: { }, resolv ...

  7. 10行代码实现简易版的Promise

    实现之前,我们先看看Promise的调用 const src = 'https://img-ph-mirror.nosdn.127.net/sLP6rNBbQhy0OXFNYD9XIA==/79910 ...

  8. 手写简易版Promise

    实现一个简易版 Promise 在完成符合 Promise/A+ 规范的代码之前,我们可以先来实现一个简易版 Promise,因为在面试中,如果你能实现出一个简易版的 Promise 基本可以过关了. ...

  9. 手写Promise简易版

    话不多说,直接上代码 通过ES5的模块化封装,向外暴露一个属性 (function(window){ const PENDING = 'pending'; const RESOLVED = 'fulf ...

随机推荐

  1. 利用正则表达式实现python强口令检测

    """ Chapter 7 模式匹配和正则表达式 1 用import re 导入正则表达式模块 2 用re.compile()函数创建一个Regex对象(记得使用原始字符 ...

  2. NOIP2018游记-退役之战

    \(Day\ 0\) 从火车站下来坐地铁\(1\)小时,再乘公交车到酒店,还要帮队里一个断腿大佬搬东西,累死我了.. 到酒店就快\(5\)点了,想打个牌也没时间. 酒店的房间很不错,空间大又干净,后来 ...

  3. [精华][推荐]CAS SSO单点登录服务端客户端学习

    1.通过下载稳定版本的方式下载cas的相关源码包,如下: 直接选择4.2.1的稳定代码即可 2.我们项目中的版本版本使用maven apereo远程库去下载 通过远程maven库下载cas-serve ...

  4. linux学习第十三天 (Linux就该这么学)找到一本不错的Linux电子书

    今天主要讲了vftp 服务的配置,不家三种访问方式   一,匿名访问模式  二,本地访问模式   三,虚拟用户模式  和,tftp简单文件传输协议 也讲了要孝试的服务,sabma服务的配置,及wind ...

  5. 【微信小程序开发之坑】javascript创建date对象

    最近开发中用到date,开始以如下方式来创建: var date = new Date('2018-01-30 11:00:00'); 在开发工具上,调试,ios 和 android都好好的. 在真机 ...

  6. 启动tomcat报错com.sun.faces.config.ConfigureListener

    小白一个,最近想着上网看看自己搭建个用maven+spring+springmvc+mybaties的框架 然后......就出来这个么东东 java.lang.ClassNotFoundExcept ...

  7. java反射获取Object的属性和值

    在看反射顺便做个笔记,目前知道的反射的Object都是要有对象的也就是实体Bean. import java.lang.reflect.Field; import java.util.ArrayLis ...

  8. mysql字符集小结

    http://blog.csdn.net/wyzxg/article/details/8779682 author:skatetime:2013/04/09 mysql字符集小结 今天同事阿杰兄发现内 ...

  9. ELK的高级篇(测试记录各种日志)

    一.elk架构已经完成情况情况下  访问限制: 加个x-pack插件  1)一个脚本收集多个日志,if 判断写入es的索引 [root@k8s6 conf.d]# cat file.conf inpu ...

  10. Batch_Size对网络训练结果的影响

    最近在跑一些网络时发现,训练完的网络在测试集上的效果总是会受Batch_Size 大小的影响.这种现象跟以往自己所想象的有些出入,于是出于好奇,各种搜博客,大致得出了自己想要的答案,现写一篇博客记录一 ...