如何从零开始利用js手写一个Promise库详解
前言
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));
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
下一篇: 美交通部长为谷歌自动驾驶汽车打气