如何从零开始利用js手写一个Promise库详解


Posted in Javascript onApril 19, 2018

前言

ECMAScript 是 JavaScript 语言的国际标准,JavaScript 是 ECMAScript 的实现。ES6 的目标,是使得 JavaScript 语言可以用来编写大型的复杂的应用程序,成为企业级开发语言。

概念

ES6 原生提供了 Promise 对象。

所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。

三道思考题

刚开始写前端的时候,处理异步请求经常用callback,简单又顺手。后来写着写着就抛弃了callback,开始用promise来处理异步问题。promise写起来确实更加优美,但由于缺乏对它内部结构的深刻认识,每次在遇到一些复杂的情况时,promise用起来总是不那么得心应手,debug也得搞半天。

所以,这篇文章我会带大家从零开始,手写一个基本能用的promise。跟着我写下来以后,你会对promise是什么以及它的内部结构有一个清楚的认知,未来在复杂场景下使用promise也能如鱼得水。

而且,为了检验大家是否真的完全掌握了promise,我会在文章结尾出几道跟promise相关的练习题。说是练习题,其实都是大家项目中会遇到的真实场景的抽象,熟练掌握可以帮助大家在前端方面更上一层楼。

提前将三道练习题给出来,大家可以先不看下文的内容,在脑海里大概构思下你会怎么解决:

  • promise array的链式调用?
  • promise怎么做并发控制?
  • promise怎么做异步缓存?

以上三道思考题其实跟你用不用promise并没有多大关系,但是如果你不深刻理解promise想要解决这三个问题还真不是那么轻松的。

什么是Promise

回到正文,什么是Promise?说白了,promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

首先,ES6规定Promise对象是一个构造函数,用来生成Promise实例。然后,这个构造函数接受一个函数(executor)作为参数,该函数的两个参数分别是resolve和reject。最后,Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数(onFulfilled和onRejected)。

具体的使用方法,用代码表现是这样:

const promise = new Promise(function(resolve, reject) {
 // ... some code

 if (/* 异步操作成功 */){
 resolve(value);
 } else {
 reject(error);
 }
});

promise.then(function(value) {
 // success
}, function(error) {
 // failure
});

理解了这个后,我们就可以大胆的开始构造我们自己的promise了,我们给它取个名字:CutePromise

实现一个Promise:CutePromise

我们直接用ES6的class来创建我们的CutePromise,对ES6语法还不熟悉的,可以先读一下我的另外两篇介绍ES6核心语法的文章后再回来。30分钟掌握ES6/ES2015核心内容(上) 、30分钟掌握ES6/ES2015核心内容(下)

class CutePromise {
 // executor是我们实例化CutePromise时传入的参数函数,它接受两个参数,分别是resolve和reject。
 // resolve和reject我们将会定义在constructor当中,供executor在执行的时候调用
 constructor(executor) {
 const resolve = () => {}
 const reject = () => {}
 executor(resolve, reject)
 }

 // 为实例提供一个then的方法,接收两个参数函数,
 // 第一个参数函数必传,它会在promise已成功(fulfilled)以后被调用
 // 第二个参数非必传,它会在promise已失败(rejected)以后被调用
 then(onFulfilled, onRejected) {}
}

创建了我们的CutePromise后,我们再来搞清楚一个关键点:Promise 对象的状态。

Promise 对象通过自身的状态,来控制异步操作。一个Promise 实例具有三种状态:

  • 异步操作未完成(pending)
  • 异步操作成功(fulfilled)
  • 异步操作失败(rejected)

上面三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。状态的切换只有两条路径:第一种是从pending=>fulfilled,另一种是从pending=>rejected,状态一旦切换就不能再改变。

现在我们来为CutePromise添加状态,大概流程就是:

首先,实例化初始过程中,我们先将状态设为PENDING,然后当executor执行resolve的时候,将状态更改为FULFILLED,当executor执行reject的时候将状态更改为REJECTED。同时更新实例的value。

constructor(executor) {
 ...
 this.state = 'PENDING';
 ...
 const resolve = (result) => {
  this.state = 'FULFILLED';
  this.value = result;
 }
 const reject = (error) => {
  this.state = 'REJECTED';
  this.value = error;
 }
 ...
}

再来看下我们的then函数。then函数的两个参数,onFulfilled表示当promise异步操作成功时调用的函数,onRejected表示当promise异步操作失败时调用的函数。假如我们调用then的时候,promise已经执行完成了(当任务是个同步任务时),我们可以直接根据实例的状态来执行相应的函数。假如promise的状态还是PENDING, 那我们就将onFulfilled和onRejected直接存储到chained这个变量当中,等promise执行完再调用。

constructor(executor) {
 ...
 this.state = 'PENDING';
 
 // chained用来储存promise执行完成以后,需要被依次调用的一系列函数
 this.chained = [];
 const resolve = (result) => {
  this.state = 'FULFILLED';
  this.value = result;
  
  // promise已经执行成功了,可以依次调用.then()函数里的onFulfilled函数了
  for (const { onFulfilled } of this.chained) {
   onFulfilled(res);
  }
 }

 ...
}
then(onFulfilled, onRejected) {
 if (this.state === 'FULFILLED') {
 onFulfilled(this.value);
 } else if (this.state === 'REJECTED') {
 onRejected(this.value);
 } else {
 this.$chained.push({ onFulfilled, onRejected });
 }
}

这样我们就完成了一个CutePromise的创建,下面是完整代码,大家可以复制代码到控制台测试一下:

class CutePromise {
 constructor(executor) {
 if (typeof executor !== 'function') {
  throw new Error('Executor must be a function');
 }

 this.state = 'PENDING';
 this.chained = [];
 const resolve = res => {
  if (this.state !== 'PENDING') {
  return;
  }

  this.state = 'FULFILLED';
  this.internalValue = res;
  for (const { onFulfilled } of this.chained) {
  onFulfilled(res);
  }
 };
 const reject = err => {
  if (this.state !== 'PENDING') {
  return;
  }
  this.state = 'REJECTED';
  this.internalValue = err;
  for (const { onRejected } of this.chained) {
  onRejected(err);
  }
 };

 try {
  executor(resolve, reject);
 } catch (err) {
  reject(err);
 }
 }
 
 then(onFulfilled, onRejected) {
 if (this.state === 'FULFILLED') {
  onFulfilled(this.internalValue);
 } else if (this.$state === 'REJECTED') {
  onRejected(this.internalValue);
 } else {
  this.chained.push({ onFulfilled, onRejected });
 }
 }
}

提供一下测试代码:

let p = new CutePromise(resolve => {
 setTimeout(() => resolve('Hello'), 100);
});
p.then(res => console.log(res));
p = new CutePromise((resolve, reject) => {
 setTimeout(() => reject(new Error('woops')), 100);
});
p.then(() => {}, err => console.log('Async error:', err.stack));
p = new CutePromise(() => { throw new Error('woops'); });
p.then(() => {}, err => console.log('Sync error:', err.stack));

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
javascript 支持链式调用的异步调用框架Async.Operation
Aug 04 Javascript
理解Javascript_13_执行模型详解
Oct 20 Javascript
理解JSON:3分钟课程
Oct 28 Javascript
比较新旧两个数组值得增加和删除的JS代码
Oct 30 Javascript
node.js中的path.isAbsolute方法使用说明
Dec 08 Javascript
JavaScript实现的一个倒计时的类
Mar 12 Javascript
JS实现的鼠标跟随代码(卡通手型点击效果)
Oct 26 Javascript
AngularJS使用angular-formly进行表单验证
Dec 27 Javascript
在AngularJS中使用jQuery的zTree插件的方法
Apr 21 Javascript
jQuery实现的瀑布流加载效果示例
Sep 13 Javascript
vue cli构建的项目中请求代理与项目打包问题
Feb 26 Javascript
bootstrapTable+ajax加载数据 refresh更新数据
Aug 31 Javascript
30分钟快速入门掌握ES6/ES2015的核心内容(下)
Apr 18 #Javascript
30分钟快速入门掌握ES6/ES2015的核心内容(上)
Apr 18 #Javascript
原生js实现拖拽功能基本思路详解
Apr 18 #Javascript
一个基于react的图片裁剪组件示例
Apr 18 #Javascript
JS实现对json对象排序并删除id相同项功能示例
Apr 18 #Javascript
Angular ng-animate和ng-cookies用法详解
Apr 18 #Javascript
JS实现的base64加密解密操作示例
Apr 18 #Javascript
You might like
php小技巧之过滤ascii控制字符
2014/05/14 PHP
PHP中模拟链表和链表的基本操作示例
2016/02/27 PHP
PHP安全下载文件的方法
2016/04/07 PHP
基于jquery的bankInput银行卡账号格式化
2012/08/22 Javascript
JS判断字符串包含的方法
2015/05/05 Javascript
jquery实现左右滑动菜单效果代码
2015/08/27 Javascript
js实现数组冒泡排序、快速排序原理
2016/03/08 Javascript
jQuery中借助deferred来请求及判断AJAX加载的实例讲解
2016/05/24 Javascript
JQueryEasyUI之DataGrid数据显示
2016/11/23 Javascript
angularjs过滤器--filter与ng-repeat配合有奇效
2017/04/20 Javascript
Angular父子组件通过服务传参的示例方法
2018/10/31 Javascript
Javascript Worker子线程代码实例
2020/02/20 Javascript
Python基于PycURL自动处理cookie的方法
2015/07/25 Python
Python的Flask框架中使用Flask-SQLAlchemy管理数据库的教程
2016/06/14 Python
python机器学习理论与实战(五)支持向量机
2018/01/19 Python
详解分布式任务队列Celery使用说明
2018/11/29 Python
Python Gitlab Api 使用方法
2019/08/28 Python
python实现两个文件夹的同步
2019/08/29 Python
详解Python3定时器任务代码
2019/09/23 Python
Python 将json序列化后的字符串转换成字典(推荐)
2020/01/06 Python
Python unittest工作原理和使用过程解析
2020/02/24 Python
浅谈django框架集成swagger以及自定义参数问题
2020/07/07 Python
Python return语句如何实现结果返回调用
2020/10/15 Python
GLAMGLOW香港官网:明星出镜前的秘密武器
2017/03/16 全球购物
澳大利亚在线百货商店:Real Smart
2017/08/13 全球购物
韩国11街:11STREET
2018/03/27 全球购物
求职简历自荐信
2013/10/20 职场文书
自荐信结尾
2013/10/27 职场文书
中国好声音广告词
2014/03/18 职场文书
动员大会主持词
2014/03/20 职场文书
廉洁校园实施方案
2014/05/25 职场文书
大学生优秀班干部事迹材料
2014/05/26 职场文书
2016年校园植树节广播稿
2015/12/17 职场文书
2016年圣诞节义工活动总结
2016/04/01 职场文书
Python多线程实用方法以及共享变量资源竞争问题
2022/04/12 Python
解决 redis 无法远程连接
2022/05/15 Redis