Promise的前世今生
在js中,异步是一个非常重要的组成部分,它基于事件循环,保证了优先级更高任务的优先执行权,比如js下载、UI渲染、js中非异步的任务,异步使得单进程的js能够做到非阻塞,这在node显得攸关重要,它使得js不必等待I/O操作返回结果,而能去处理其他任务。但是异步也存在着缺点,最明显的就是回地狱,而Pormise规范的出现,让开发者从回调地狱从解放出来。它由社区提出和实现,之后被es6加入到标准当中。
Promise的组成
Promise最基本的是由status、resolve、onResolved、reject、onRejected、then和catch几部分组成:
status:状态管理,有pendding,fulfilled和rejected三种状态,且状态一经改变是无法逆转的;
resolve: 一个函数,当调用该函数时,说明异步任务执行成功了,promise的status由pendding转为fulfilled,且会调用成功的回调函数onResolved;
reject: 一个函数,当调用该函数时,说明异步任务执行失败,promise的status由pendding转为rejected,且会调用失败的回调函数onRejected;
then: 一个函数,在then的参数里面,我们需要定义回调成功需要执行的成功回调函数,promise会将这个成功回调函数注册到onResolved,由resolve触发调用,并且还支持链式调用;
catch: 一个函数,在reject的参数里面,我们需要回调失败需要执行的失败回调函数,promise会将这个失败回调函数注册到onRejected,由reject触发调用;
备注: 这里没讲race和all方法等,有兴趣的可以去看下文档。
使用方法
通过new创建一个Promise对象,传入一个函数参数,这个函数包含resolve和reject参数,这两个参数,如上所述也是函数,当异步任务完成的时候,调用resolve方法,当异步任务失败的时候,调用reject方法。eg:
var p = new Promise(function(resolve, reject){
setTimeout(function() {
resolve(1)
}, 1000)
})
then
方法,参数也是一个回调函数,当你调用resolve方法后,这个回调函数就会调用,且resolve函数传入的参数,会带给这个回调函数的参数,eg:
p.then(function(arg){
console.log(arg) // 1
})
catch
方法,参数同样是一个回调函数,当你调用reject方法后,这个回调函数就会调用,且reject函数传入的参数,会带给这个回调函数,使用例子和then同理。
所以我们的Promise的调用可以是这样的:
var p = new Promise(function(resolve, reject){
// 异步任务执行,且看结果调用resolve还是reject
}).then(function(){
// do something1
return new Promise...
}).then(function() {
// do something2
return new Promise....
}).then(function() {
// do something3
return new Promise....
}).then(function() {
// do something4
return new Promise....
})
对比用回调的方式
doAsyncTask1(function(){
// do something1
doAsyncTask2(function() {
// do something2
doAsyncTask3(function() {
// do something3
doAsyncTask4(function(){
// do something4
})
})
})
})
是不是比起异步编程,promise更人性化呢?
async await
上面promise的调用方式其实还不够优雅,还有更加优雅的调用方式,那就是async await方式,我们来看下如何用。
async testPromise() {
var result = await new Promise(function(resolve, reject){
setTimeout(function(){
resolve(1)
}, 2000)
})
console.log(result)
var result1 = await new Promise(function(resolve, reject){
setTimeout(function(){
resolve(1)
}, 2000)
})
console.log(result1)
}
是不是已经变成了我们同步的编程方式了?
这里有两点需要注意的:
1、 await关键字必须在使用async修饰的方法中才能使用,反之则没问题。
2、 async不能直接使用,需要使用babel进行转译,一般需要借助webpack进行配置,不能单独在js中使用,这个相对来说比较麻烦。
如何实现自己实现Promise
面试官很喜欢问你如何实现一个Promise,这个时候我们就需要对Promise有一定的理解,我们不去看Promise/A+规范,太复杂晦涩难懂了,我们就按照上面讲的Promise的组成来实现一个简单的Promise。
function _Promise(fn) {
this.status = 'pending';
this.onResolved = null;
this.onRejected = null;
fn.call(this, this.resolve.bind(this), this.reject.bind(this))
}
_Promise.prototype.resolve = function(arg) {
if(this.status === 'pending') {
this.onResolved(arg)
this.status = 'fulfilled'
}
}
_Promise.prototype.reject = function(arg) {
if(this.status === 'pending') {
this.onRejected(arg)
this.status = 'rejected'
}
}
_Promise.prototype.then = function(onResolved) {
this.onResolved = onResolved
}
_Promise.prototype.reject = function(onRejected) {
this.onResolved = onRejected
}
这就是一个最简单的Promise实现方式,但是它还不支持链式调用。所以我们需要在then方法再返回一个Promise, 我们改造一下:
function _Promise(fn) {
this.status = 'pending';
this.onResolved = null;
this.onRejected = null;
this.childReject = null;
this.childResolve = null;
fn.call(this, this.resolve.bind(this), this.reject.bind(this))
}
_Promise.prototype.resolve = function(arg) {
if(this.status === 'pending') {
if(this.onResolved) {
var ret = this.onResolved(arg)
// 如果第二个then return的是一个用户自定义的promise,我们称为PromiseB,则需要把childResolve赋值给这个PromiseB的onResolved,交给这个PromiseB来执行
if(ret instanceof _Promise) {
ret.onResolved = this.childResolve
return
}
// 否则直接调用childResove确保第二个then的回调执行
this.childResolve && this.childResolve()
}
this.status = 'fulfilled'
}
}
_Promise.prototype.reject = function(arg) {
if(this.status === 'pending') {
this.onRejected && this.onRejected(arg)
this.childReject && this.childReject()
this.status = 'rejected'
}
}
_Promise.prototype.then = function(onResolved) {
this.onResolved = onResolved
var that = this
// 定义一个promise,我们简称PromiseA,以便链式调用
return new _Promise(function(resolve, reject){
// 这里需要确保resolve方法在第一个promise的resolve调用后调用,那么需要把它保存到第一个promise里面
that.childResolve = resolve
that.childReject = reject
})
}
_Promise.prototype.reject = function(onRejected) {
this.onResolved = onRejected
}
// 例子
new _Promise((resolve) => {
setTimeout(() => {
resolve()
}, 2000)
}).then(() => {
console.log('promise success')
return new _promise((resolve) => {
setTimeout(() => {
resolve()
}, 1000)
})
}).then(() => {
console.log('promise2 success')
})
这个实现,有三个Promise,第一个是首个Promise,第二个PormiseA即使如果then方法没有返回用户自定义的Promise的时候,我们方便链式调用的Promise,第三个PromiseB是then方法返回用户自定义的Promise的时候,我们需要把第二个then的回调交还给PromiseB执行。