阅读 472

一文读懂Promise

什么是Promise

$.ajax({
	success: (res) => {
		$.ajax({
            success: (res) => {
				$.ajax({
                    success: (res) => {
						//...
                    }
                })
            }
        })
	}
})
复制代码

这就是典型的回调地狱,不仅代码臃肿,可读性差,而且耦合度过高,不易维护。代码无法复用,还容易隐藏bug。

Promise规范的出现就是为了解决这种问题。

这里强调一下,Promise是一种解决方案,它是一种规范。

ES6原生提供了Promise对象,在日常开发中经常会接触到Promise相关操作,本文将介绍Promise的使用方法及相关原理,希望能有所帮助。

Promise有三种状态:

  • Pending(等待态)
  • Fulfilled(成功态)
  • Rejected(失败态)

Promise的特点是状态只能由Pending转换为Fulfilled或Rejected,并且一旦改变不再更改。

new Promise((resolve,reject) => {
	/*executor*/
})
复制代码

Promise是一个带有resolve,reject两个参数的构造函数(executor)。这个构造函数在Promise创建的时候立即执行,resolve和reject两个函数在被调用时,分别将Promise的状态更改为fulfilled(成功态)和rejected(失败态),并传递成功的值或失败的原因。executor内部通常会执行一些异步操作,当异步操作执行完毕时,可根据执行结果相应地调用resolve或reject(可能成功可能失败)。如果executor抛出一个错误,那么该Promise也将转为失败态。

Promise原型上还具有then和catch方法,调用后返回一个Promise实例,因此可以被链式调用。

每一个Promise实例都带有一个then方法,该方法有两个参数(onFulfilled,onRejected),分别为Promise成功和失败时的回调。

let p = new Promise((resolve,reject) => {
	resolve('success');
})
p.then(value => {
	console.log(value);		//success
}).then(value => {
    console.log(value);		//没有返回值时,undefined
})
复制代码

当返回了一个值或者一个成功态的Promise时,then返回的Promise都是成功态,若没有返回值时也是成功态,而回调函数的参数值为undefind

当抛出一个错误或者返回一个失败态Promise,then返回的Promise都是失败态。

let p = new Promise((resolve,reject) => {
	reject('fail');
})
p.then(() => {
	
},err => {
	console.log(err);		//fail
});
复制代码

then方法传递的成功/失败函数,这两个函数可以返回一个promise;如果返回的是一个promise,则该promise状态将作为下一次then的结果。

let p = new Promise((resolve,reject) => {
	resolve('success')
});
let p2 = new Promise((resolve,reject) => {
    resolve('success2')
})
p.then(value => {
    return p2;				//p2的成功态将作为下一次then的状态
}).then(value => {
    console.log(value)
})
复制代码

Promise.prototype.catch方法返回也是一个Promise,使用方式同then方法,用于处理异常捕获情况。该方法捕获属于就近捕获原则,离错误最近的一个catch将会捕获错误并进行处理,后续将不再捕获。

若失败状态被then方法中失败函数回调触发,则catch方法将不进行捕获。

let p1 = new Promise((resolve,reject) => {
	resolve('success');
});
p1.then(value => {
    throw 'a new error';
}).catch(e => {
    console.log(e);		//a new error
}).then(() => {
    console.log('after a catch the chain is restored');
})
复制代码

Promise.all方法返回一个新的Promise对象,在参数对象中所有的promise对象都成功时才会触发成功态,一旦有任意一个对象失败则立即触发该Promise对象的失败态。当触发成功态后,会把一个包含所有Promise返回值的数组按顺序返回。如果触犯失败态,则会把第一个失败的Promise对象错误信息返回。

let p1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('p1')
    },0)
});
let p2 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('p2')
    },0)
});
let promise = Promise.all([p1,p2]).then(value => {
    console.log(value);		//['p1','p2']
})
复制代码

Promise.race方法是把参数中任意第一个完成状态转换的Promise作为成功或失败状态,并返回该Promise对象。

let p1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('p1')
    },1000)
});
let p2 = new Promise((resolve,reject) => {
    setTimeout(() => {
        reject('p2')
    },0)
});
Promise.race([p1,p2]).then((value) => {
    console.log(value)
},(err) => {
    console.log(err);		//p2
})
复制代码

Promise.resolve(value)返回一个成功态,并且将value传递给对应的then方法;

Promise.reject(reason)返回一个失败态,并且将reason传递个对应的then方法。

Promise的优缺点

优点 缺点
解决回调 无法监测进行状态
链式调用 新建立即执行且无法取消
减少嵌套 内部错误无法抛出

Promise练习(Node环境)

1.等待状态

const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 1000)
})
const promise2 = promise1.then(() => {
    return 'an error'
})

console.log('promise1', promise1)
console.log('promise2', promise2)

setTimeout(() => {
    console.log('promise1', promise1)
    console.log('promise2', promise2)
}, 2000)
复制代码

2.状态改变

const promise = new Promise((resolve, reject) => {
    resolve('success1')
    reject('error')
    resolve('success2')
})
promise.then((res) => {
	console.log('then: ', res)
})
.catch((err) => {
	console.log('catch: ', err)
})
复制代码

3.链式调用

Promise.resolve(1)
    .then((res) => {
        console.log(res)
        return 2
    })
    .catch((err) => {
        return 3
    })
    .then((res) => {
        console.log(res)
    })
复制代码

4.链式调用

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('executor')
        resolve('success')
    }, 1000)
})

const start = Date.now()
promise.then((res) => {
    console.log(res, Date.now() - start)
})
promise.then((res) => {
    console.log(res, Date.now() - start)
})
//Promise的构造函数只执行一次,并且立即执行;promise的then方法可以被多次调用,每次都返回新的promise实例。
复制代码
const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
        console.log('executor')
        resolve('success')
    }, 1000)
})
const promise2 = promise.then(res => {
    console.log(res);
})
promise2.then(res => {
    console.log(res)
})
复制代码

5.错误捕获

Promise.resolve()
    .then(() => {
        return new Error('error!!!')
    })
    .then((res) => {
        console.log('then: ', res)
    })
    .catch((err) => {
        console.log('catch: ', err)
    })
复制代码
Promise.resolve()
    .then(() => {
        throw new Error('error');		//return Promise.reject('error')
    })
    .then((res) => {
        console.log('then: ', res)
    })
    .catch((err) => {
        console.log('catch: ', err)
    })
复制代码

then或catch方法返回一个错误对象并不会被捕获,只有在抛出错误或返回失败态时才会引起捕获。

6.引用错误

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(e => {
	console.log(e)
})
//[TypeError: Chaining cycle detected for promise #<Promise>]
复制代码

返回值不能是promise实例本身,因为此时实例并未构建完成,造成死循环,因此抛出类型错误。

7.事件环

process.nextTick(() => {
    console.log('nextTick')
})
Promise.resolve()
    .then(() => {
        console.log('then')
    })
setImmediate(() => {
    console.log('setImmediate')
})
console.log('end')

复制代码

8.穿透

Promise.resolve(1)
    .then(2)
    .then(Promise.resolve(3))
    .then(console.log)
复制代码

then和catch方法的参数是一个函数,若非函数则发生穿透

Promise A+规范

规范:promisesaplus.com/

该网站介绍了若需要实现Promise需要实现的规范方法。当多个库实现自己的Promise类时,为了能实现相应的效果,该规范提供了实现标准,并提供了promises-aplus-tests测试包测试实现能力。

后续将推出文章介绍如何实现自己的Promise类,敬请期待!

前端优选