Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了
Promise
对象。所谓
Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise
对象有以下两个特点。(1)对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。注意,为了行文方便,本章后面的
resolved
统一只指fulfilled
状态,不包含rejected
状态。有了
Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易。
Promise
也有一些缺点。首先,无法取消Promise
,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部。第三,当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
根据上面的消息和定义;我们先写个简单的,看着有点不像的MyPromise;
function MyPromise(fn) { function resolve(value) { console.log(value); } function reject(value) { console.log(value); } fn(resolve, reject); }
现在你就可以用上自定义的Promise了
new MyPromise((resolve, reject) => { setTimeout(()=>{ resolve('你将看到两秒后的我'); }, 2000); });
//将会在2秒后输出
解释一下整体代码:
MyPromise 中的参数 fn是需要用户传入的自定义函数(该函数需要接收两个参数)。
MyPromise 中的内部还有两个函数resolve和reject,其中 resolve 表示用户的异步任务成功时应该调用的函数,reject 表示用户的异步任务失败时该调用的函数。
那用户如何调用 resolve 和 reject 呢?
很简单,把两个函数当作 fn的参数传递出去即可。
所以 MyPromise 内部在调用 fn 时会把 resolve 和 reject当作参数传递给 fn。
然后用户在自定义函数内调用 resolve 或 reject 来通知 MyPromise 异步任务已经执行完了。
通过上面的代码可以发现一个问题,我们不知道Promise的异步任务进行到哪一步了、是成功还是失败了。
所以增加三个状态用来标识一个Promise的异步任务进行到何种程度了。
pending、resolved、rejected 分别表示 执行中、已完成、已失败。
然后通过观察用户调用的是 resolve 还是 reject 可以判断当前Promise的状态。 那么会有三种情况:
- 在用户调用 resolve 或 reject 之前状态是 pending
- 用户调用 resolve 时,状态将变为 resolved
- 用户调用 reject 时,状态将变为 rejected
下面进行代码的改造,定义了三个常量表示状态以及一个变量 state 用来存储当前状态。 并且当 resolve 被调用时将 state 修改为 resolved 。
const PEDNING="pending";//执行状态 const RESOLVED='resolved';//以完成; const REJECTED='rejected';//以失败 function MyPromise(fn){ const that=this //初始状态为执行中,pending this.state=PEDNING; function resolve(value){ console.log(value) that.state=RESOLVED; } function reject(err){ console.log(err) that.state=REJECTED; } fn(resolve,reject); }
OK,现在已经能知道Promise当前所处的状态了,但是任务完了得拿到结果吧,MyPromise 的 resolve 被调用,那也只是MyPromise知道任务完成了,用户还不知道呢。 所以我们需要回调函数告诉用户,是的,其实就是回调函数。 这时候就轮到 then 方法出场了,用户通过then方法传入回调函数, MyPromise 将在成功调用 resolve 时调用用户传入的回调函数。 开始改造代码,MyPromise 内部需要变量存储回调函数,then 方法的作用就是将用户传入的回调函数赋予 MyPromise 内的变量。 所以 then 方法长这样,接收两个参数,一个是成功时的回调函数,一个是失败时的回调函数
const PEDNING="pending";//执行状态 const RESOLVED='resolved';//以完成; const REJECTED='rejected';//以失败 function MyPromise(fn){ const that=this //初始状态为执行中,pending this.state=PEDNING; //两个储存回调函数的变量 this.resolvedCallback; this.rejectedCallback; function resolve(value){ that.state=RESOLVED; that.resolvedCallback && that.resolvedCallback(value); } function reject(err){ that.state=REJECTED; that.rejectedCallback && that.rejectedCallback(err); } fn(resolve,reject); } MyPromise.prototype.then=function(onFulfilled,onRejected){ this.resolvedCallback = onFulfilled; this.rejectedCallback = onRejected; }
是的,一个简版Promise几乎大功告成,让我们再试试在浏览器执行如下代码(注意我删除了 resolve 和 reject 里的console语句);咱们来使用一下:
(function(){ const PEDNING="pending";//执行状态 const RESOLVED='resolved';//以完成; const REJECTED='rejected';//以失败 function MyPromise(fn){ const that=this //初始状态为执行中,pending this.state=PEDNING; //两个储存回调函数的变量 this.resolvedCallback; this.rejectedCallback; function resolve(value){ that.state=RESOLVED; that.resolvedCallback && that.resolvedCallback(value); } function reject(err){ that.state=REJECTED; that.rejectedCallback && that.rejectedCallback(err); } fn(resolve,reject); } MyPromise.prototype.then=function(onFulfilled,onRejected){ this.resolvedCallback = onFulfilled; this.rejectedCallback = onRejected; } new MyPromise((resolve,reject)=>{ setTimeout(()=>{ resolve('我是结果') },4000); }).then((value)=>{ console.log(value) }) })()
通过匿名函数和函数自执行,形成局部作用域,保护里面的变量;
上面的代码,用法上已经和Promise长得差不多了,但是如果我们多次调用 then 方法呢? 是的,只有最后一个 then 方法里的回调函数能执行,这当然没法满足我们的需要。 于是,将两个回调函数改成函数数组(请回想一下前置知识),并在状态更改时遍历调用回调函数。 改造后的代码如下:
(function(){ const PEDNING="pending";//执行状态 const RESOLVED='resolved';//以完成; const REJECTED='rejected';//以失败 function MyPromise(fn){ const that=this //初始状态为执行中,pending this.state=PEDNING; //两个储存回调函数的变量,注意,变成了数组 this.resolvedCallbackList=[]; this.rejectedCallbackList=[]; function resolve(value){ that.state=RESOLVED; that.resolvedCallbackList && that.resolvedCallbackList.forEach(cbFn =>cbFn(value)); } function reject(err){ that.state=REJECTED; that.rejectedCallbackList && that.rejectedCallbackList.forEach(cbFn =>cbFn(err)); } fn(resolve,reject); } MyPromise.prototype.then=function(onFulfilled,onRejected){ this.resolvedCallbackList.push(onFulfilled); this.rejectedCallbackList.push(onRejected); // 这里是为了链式调用,所以要返回this,即myPromise.then().then().then()... return this } new MyPromise((resolve,reject)=>{ setTimeout(()=>{ resolve('经过5秒出现') },5000) }).then((val)=>{ console.log(val+'第一次出现的value') }).then((val)=>{ console.log(val+'第二次出现的value') }) })()
上面已经是简版Promise的实现了。 但是我们还可以更完善一点,增强 MyPromise 的健壮性。 例如,若用户自定义函数在执行过程中发生了错误,会中断程序的执行,于是我们增加try...catch...语句,并在发生错误时主动执行reject函数告知用户。
try { fn(resolve, reject); } catch (e) { reject(e); }
又或者,对参数进行校验,状态进行判断等,以 then为例,若用户传入的参数不是函数呢? 或者Promise的状态已经时rejected或resolved,此时调用then呢?
改造 then 后代码如下:
MyPromise.prototype.then = function(onFulfilled, onRejected) { if(typeof onRejected !== 'function') { onRejected = v => v; } if(typeof onFulfilled !== 'function') { onFulfilled = v => { throw r }; } const that = this; if (that.state === PENDING) { that.resolvedCallbacks.push(onFulfilled) that.rejectedCallbacks.push(onRejected) } if (that.state === RESOLVED) { onFulfilled(that.value) } if (that.state === REJECTED) { onRejected(that.value) } }