如何从零开始利用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 相关文章推荐
jqueyr判断checkbox组的选中(示例代码)
Nov 08 Javascript
javascript拖拽上传类库DropzoneJS使用方法
Dec 05 Javascript
Javascript高级技巧分享
Feb 25 Javascript
JavaScript简单表格编辑功能实现方法
Apr 16 Javascript
jQuery源码分析之init的详细介绍
Feb 13 Javascript
详解react-native-fs插件的使用以及遇到的坑
Sep 12 Javascript
利用pm2部署多个node.js项目的配置教程
Oct 22 Javascript
基于Vue实现可以拖拽的树形表格实例详解
Oct 18 Javascript
mpvue实现小程序签到金币掉落动画(api实现)
Oct 17 Javascript
jQuery实现鼠标移入显示蒙版效果
Jan 11 jQuery
vue实现lodop打印功能的示例
Nov 11 Javascript
vue实现滚动鼠标滚轮切换页面
Dec 13 Vue.js
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 计算两个时间戳相隔的时间的函数(小时)
2009/12/18 PHP
支持数组的ADDSLASHES的php函数
2010/02/16 PHP
PHP获取本周第一天和最后一天示例代码
2014/02/24 PHP
5款适合PHP使用的HTML编辑器推荐
2015/07/03 PHP
详解PHP swoole process的使用方法
2017/08/26 PHP
js实现新浪微博首页效果
2015/10/16 Javascript
BootStrap整体框架之基础布局组件
2016/12/15 Javascript
js实现显示手机号码效果
2017/03/09 Javascript
es6系列教程_ Map详解以及常用api介绍
2017/09/25 Javascript
详解从零搭建 vue2 vue-router2 webpack3 工程
2017/11/22 Javascript
使用jQuery 操作table 完成单元格合并的实例
2017/12/27 jQuery
vue实现的仿淘宝购物车功能详解
2019/01/27 Javascript
利用Dectorator分模块存储Vuex状态的实现
2019/02/05 Javascript
vue.config.js常用配置详解
2019/11/14 Javascript
vue2路由方式--嵌套路由实现方法分析
2020/03/06 Javascript
查找Vue中下标的操作(some和findindex)
2020/08/12 Javascript
[15:56]Heroes18_暗影萨满(完美)
2014/10/31 DOTA
Python内置函数的用法实例教程
2014/09/08 Python
win与linux系统中python requests 安装
2016/12/04 Python
浅谈Python大神都是这样处理XML文件的
2019/05/31 Python
Django实现文件上传和下载功能
2019/10/06 Python
Django xadmin开启搜索功能的实现
2019/11/15 Python
python使用docx模块读写docx文件的方法与docx模块常用方法详解
2020/02/17 Python
Python的轻量级ORM框架peewee使用教程
2021/02/05 Python
The Hut德国站点:时装、家居用品、美容等
2016/09/23 全球购物
Agoda中文官网:安可达(低价预订全球酒店)
2021/01/18 全球购物
男方父母证婚词
2014/01/12 职场文书
国际贸易专业个人求职信格式
2014/02/02 职场文书
微笑面对生活演讲稿
2014/05/13 职场文书
计算机售后服务承诺书
2014/05/30 职场文书
2015年七七事变78周年纪念活动方案
2015/05/06 职场文书
行为习惯主题班会
2015/08/14 职场文书
Python requests库参数提交的注意事项总结
2021/03/29 Python
详解Redis基本命令与使用场景
2021/06/01 Redis
台式电脑蓝牙适配器怎么安装?台式电脑蓝牙适配器安装教程
2022/04/08 数码科技
Vite + React从零开始搭建一个开源组件库
2022/06/25 Javascript