做为ES6处理异步操做的新规范,Promise一经出现就广受欢迎。面试中也是如此,固然此时对前端的要求就不只仅局限会用这个阶段了。下面就一块儿看下Promise相关的内容。javascript
在开始以前,仍是简单回顾下Promise是什么以及怎么用,直接上来谈实现有点空中花园的感受。(下面示例参考自阮大佬es6 Promis,)前端
Promise 是异步编程的一种解决方案,能够认为是一个对象,能够从中获取异步操做的信息。以替代传统的回调事件。java
es6规范中,Promise是个构造函数,因此建立以下:git
const promise = new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'resolve');
// 能够为同步,以下操做
return resolve('resolve')
})
复制代码
注意resolve或者reject 一旦执行,后续的代码能够执行但就不会再更新状态(不然这状态回调就没法控制了)。 举个例子:es6
var a = new Promise((resolve,reject)=>{
resolve(1)
console.log('执行代码,改变状态')
throw new Error('ss')
})
a.then((res)=>{
console.log('resolved >>>',res)
},(err)=>{
console.log('rejected>>>',err)
})
// 输出
// 执行代码,改变状态
// resolved >>> 1
复制代码
所以,状态更新函数以后的再次改变状态的操做都是无效的,例如异常之类的也不会被catch。 逻辑代码推荐在状态更新以前执行。github
构造函数接收一个函数,该函数会同步执行,即咱们的逻辑处理函数,什么时候执行对应的回调,这部分逻辑仍是要本身管理的。面试
至于如何执行回调,就和入参有关系了。
两个入参resolve和reject,分别更新不一样状态,以触发对应处理函数。 触发操做由Promise内部实现,咱们只关注触发时机便可编程
那么要实现一个Promise,其构造函数应该是这么个样子:json
// 三种状态
const STATUS = {
PENDING: 'pending',
RESOLVED:'resolved',
REJECTED:'rejected'
}
class Promise{
constructor(fn){
// 初始化状态
this.status = STATUS.PENDING
// resolve事件队列
this.resolves = []
// reject事件队列
this.rejects = []
// resolve和reject是内部提供的,用以改变状态。
const resovle = (val)=>{
// 显然这里应该是改变状态触发回调
this.triggerResolve(val)
}
const reject = (val)=>{
// 显然这里应该是改变状态触发回调
this.triggerReject(val)
}
// 执行fn
try{
fn(resolve,reject)
}catch(err){
// 运行异常要触发reject,就须要在这里catch了
this.triggerReject(err)
}
}
then(){
}
}
复制代码
触发回调的triggerReject/triggerResolve 作的事情主要两个:promise
// 触发 reject回调
triggerReject(val){
// 保存当前值,以供后面调用
this.value = val
// promise状态一经变化就再也不更新,因此对于非pending状态,再也不操做
if (this.status === STATUS.PENDING) {
// 更新状态
this.status = STATUS.REJECTED
// 循环执行回调队列中事件
this.rejects.forEach((it) => {
it(val)
})
}
}
// resolve 功能相似
// 触发 resolve回调
triggerResolve(val) {
this.value = val
if(this.status === STATUS.PENDING){
this.status = STATUS.RESOLVED
this.resolves.forEach((it,i)=>{
it(val)
})
}
}
复制代码
此时执行的话仍是不能达到目的的,由于this.resolves/ this.rejects的回调队列里面仍是空呢。 下面就看如何会用then往回调队列中增长监听事件。
该方法为Promise实例上的方法,做用是为Promise实例增长状态改变时的回调函数。 接受两个参数,resolve和reject即咱们所谓成功和失败回调,其中reject可选
then方法返回的是一个新的实例(也就是新建了一个Promise实例),可实现链式调用。
new Promise((resolve, reject) => {
return resolve(1)
}).then(function(res) {
// ...
}).then(function(res) {
// ...
});
复制代码
前面的结果为后边then的参数,这样能够实现次序调用。 若前面返回一个promise,则后面的then会依旧遵循promise的状态变化机制进行调用。
看起来也简单,then是往事件队列中push事件。那么很容易得出下面的代码:
// 两个入参函数
then(onResolved,onRejected){
const resolvehandle=(val)=>{
return onResolved(val)
},rejecthandle =(val)=>{
return onRejected(val)
}
// rejecthandle
this.resolves.push(resolvehandle)
this.rejects.push(rejecthandle)
}
复制代码
此时执行示例代码,能够获得结果了。
new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'done');
}).then((res)=>{
console.log(res)
}) // done
复制代码
不过这里太简陋了,并且then还有个特色是支持链式调用其实返回的也是promise 对象。 咱们来改进一下。
then(onResolved,onRejected){
// 返回promise 保证链式调用,注意这里每次then都新建了promise
return new Promise((resolve,reject)=>{
const resolvehandle = (val)=>{
// 对于值,回调方法存在就直接执行,不然不变传递下去。
let res = onResolved ? onResolved(val) : val
if(Promise.isPromise(res)){
// 若是onResolved 是promise,那么就增长then
return res.then((val)=>{
resolve(val)
})
}else {
// 更新状态,执行完了,后面的随便
return resolve(val)
}
},
rejecthandle = (val)=>{
var res = onRejected ? onRejected(val) : val;
if (Promise.isPromise(res)) {
res.then(function (val) {
reject(val);
})
} else {
reject(val);
}
}
// 正常加入队列
this.resolves.push(resolvehandle)
this.rejects.push(rejecthandle)
})
}
复制代码
此时链式调用和promise 的回调也已经支持了,能够用以下代码测试。
new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'done');
}).then((res)=>{
return new Promise((resolve)=>{
console.log(res)
setTimeout(resolve, 200, 'done2');
})
}).then((res)=>{
console.log('second then>>', res)
})
复制代码
不过此时对于同步的执行,仍是有些问题。 由于then中的实现,只是将回调事件假如回调队列。
对于同步的状态,then执行在构造函数以后, 此时事件队列为空,而状态已经为resolved,
因此这种状态下须要加个判断,若是非pending状态直接执行回调。
then(onResolved,onRejected){
/**省略**/
// 刚执行then 状态就更新,那么直接执行回调
if(this.status === STATUS.RESOLVED){
return resolvehandle(this.value)
}
if (this.status === STATUS.REJECTED){
return rejecthandle(this.value)
}
})
}
复制代码
这样就能解决同步执行的问题。
new Promise((resolve, reject) => {
resolve('done')
}).then((res)=>{
console.log(res)
})
// done
复制代码
catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。 直接看例子比较简单:
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
复制代码
此时catch是是getJSON和第一个then运行时的异常,若是只是在then中指定reject函数,那么then中执行的异常没法捕获。 由于then返回了一个新的promise,同级的reject回调,不会被触发。 举个例子:
var a = new Promise((resolve,reject)=>{
resolve(1)
})
a.then((res)=>{
console.log(res)
throw new Error('then')
},(err)=>{
console.log('catch err>>>',err) // 不能catch
})
复制代码
该catch只能捕获构造函数中的异常,对于then中的error就不能捕获了。
var a = new Promise((resolve,reject)=>{
resolve(1)
})
a.then((res)=>{
console.log(res)
throw new Error('then')
}).catch((err)=>{
console.log('catch err>>>',err) // catch err>>> Error: then at <anonymous>:6:11
})
复制代码
推荐每一个then以后都跟catch来捕获全部异常。
基于catch方法是.then(null, rejection)或.then(undefined, rejection)的别名这句话,其实实现就比较简单了。 其内部实现调用then就能够了。
catch(onRejected){
return this.then(null, onRejected)
}
复制代码
该方法为获取一个指定状态的Promise对象的快捷操做。 直接看例子比较清晰:
Promise.resolve(1);
// 等价于
new Promise((resolve) => resolve(1));
Promise.reject(1);
// 等价于
new Promise((resolve,reject) => reject(1));
复制代码
既然是Promise的自身属性,那么能够用es6的static来实现: Promise.reject与其相似,就再也不实现了。
// 转为promise resolve 状态
static resolve(obj){
if (Promise.isPromise(obj)) {
return obj;
}
// 非promise 转为promise
return new Promise(function (resolve, reject) {
resolve(obj);
})
}
复制代码
阮一峰es6入门
promisesaplus.com/
liubin.org/promises-bo…
本想把常见的promise面试题一块儿加上的,后面就写成了promise的实现,手动Promise均可以实现的话,相关面试题应该问题不大。这里附一个JavaScript | Promises interiew 你们能够看看。完整代码请戳