JavaScript Promise 学习记录(一)
本文首先介绍了promise的三种状态及生成不同状态promise方法,然后介绍了promise的回调处理方法then,分析了不同情况下then函数返回的promise状态。 最后通过promise链,将以上知识点进行串联。希望能够对大家有所帮助。由于作者的能力有限,也正在处于学习阶段,文中不免会有错误,欢迎读者指正。
Promise的三种状态
Promise 有三种状态: 未决议(pending)、已决议(resolved)、已拒绝(rejected)
生成Promise的方式有 new Promise() 、 Promise.resolve() 、Promise.reject()
在promise实例上调用then或catch方法也会生成promise对象,故可进行promise链,在下一节会有所介绍。
tip: chrome浏览器的控制台,是一个快速验证js代码的不错选择,读者可以打开chrome浏览器,边阅读边实践。以下代码皆可直接复制,进行验证。
首先我们在chrome控制台中,实践下生成三种不同状态的promise的方法:
//生成pedding状态的promise,在控制台输入:
new Promise ((resolve, reject) => {
// do nothing
})
//控制台输出
//Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
//生成已决议状态的promise:
new Promise ((resolve, reject) => {
resolve(123)
})
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}
//或是
Promise.resolve(123)
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}
//生成拒绝状态的promise
new Promise ((resolve, reject) => {
reject(456)
})
//Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}
//或是
Promise.reject(456)
//Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}
以上创建的都是立即决议的promise,在我们实际的场景中,Promise往往需要一段时间来处理任务,再完成决议。我们来模拟下这个过程:
var p = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(123)
}, 5000)
})
输入完以上代码后,我们立即在控制台输入p,查看p对象
p
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
5秒后,我们再次输入p,
p
//Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}
以上,我们创建了一个Promise对象,该对象创建时,处于pending状态,我们模拟了一段耗时操作,在5秒后,对该promise进行决议 resolve(123).我们再次查看p时,发现状态已经变为resolved。
Promise的回调
在promise上可以调用then方法进行回调的注册。promise采用了正确与错误回调分流的方式进行分别回调。区别于error-first方式。
首先看下error-first方式的回调处理方法:
var callback = function(error, value) {
//在回调中首先判断是否有错误,如果有错误,进行处理
if (error) {
console.log(error)
return
}
//无错误,正常处理
console.log('get value :' + value)
}
function foo (key, callback) {
// do something
if (key == 1) {
callback('key not exit', null) //存在错误的情况
} else if (key == 2) {
callback(null, 3) // 正常处理的情况
}
}
foo(1, callback) // key not exit
foo(2, callback) // get value :3
- promise 采用正常与错误结果分流回调的处理设计
var p = new Promise((resolve, reject) => {
resolve(123)
})
p.then(fullfilled => {
console.log(fullfilled) //123
}, error => {
console.log(error) //不会调用到这里
}
)
细心的读者会看到在控制台除了打印了 123外,还打印了 Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}。还记得我们前面提到then方法也会生成promise吗,这里打印的就是调用then方法生成的promise,我们可以先跳过,后面详细介绍。
以上我们创建了一个promise,并且通过调用then方法,在该promise注册了两个回调函数用于监听正确与失败的处理结果。由于该promise 通过resolve(123)进行了决议,所以我们在then上注册的第一个函数,会收到结果,打印123。
不难想象如果promise做出了拒绝的处理,或者内部有错误,我们在then上注册的错误处理函数将被调用。来看下例子
var p = new Promise((resolve, reject) => {
reject(456)
})
p.then(fullfilled => {
console.log(fullfilled) //不会调用到这里
}, error => {
console.log(error) //456
}
)
//或者
var p = new Promise((resolve, reject) => {
foo() //由于未定义foo, 会抛出错误
resolve(123) // 无法执行到
})
p.then(fullfilled => {
console.log(fullfilled) //不会调用这里
}, error => {
console.log(error) //ReferenceError: foo is not defined
}
)
我们看到在传递给promise的匿名函数中,我们调用了foo,由于未定义该方法,所以抛出了错误。我们可以把抛出错误也理解为promise做出了reject决议,并且拒绝的理由是抛出的错误信息。
我们看到foo()后面一句,调用了resolve,由于错误的抛出,这行实际没有被执行到。我们来猜测下,如果不是抛出错误,而是调用了reject(456),再调用resolve(123),resolve会生效吗?我们注册的2个回调都会被执行吗?
var p = new Promise((resolve, reject) => {
reject(456)
resolve(123)
})
p.then(fullfilled => {
console.log(fullfilled) //不会调用这里
}, error => {
console.log(error) //456
}
)
看来是不行,如果是多次调用resolve呢?
var p = new Promise((resolve, reject) => {
resolve(123)
resolve(456)
})
p.then(fullfilled => {
console.log(fullfilled) //123
}, error => {
console.log(error) //不会调用这里
}
)
看来resolve的结果并没有被覆写。
看了上面的例子,引述下书上的结论吧:promise一旦决议,即由peding状态变为 resolve或reject状态,其状态及值就不会再次改变。这点保证了,后续在同一个promise上调用then方法注册的回调函数都将收到相同的结果。
Promise链
上面提到在promise上调用then方法,会返回新的promise。那新的promise的值和状态是什么呢?
答案是取决于执行then中注册的回调方法时的返回值。
1.无return
相当于调用了return Promise.resolve(undefined),
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}
也就是我们上文中,调用then方法时,除了打印回调中的日志,还打印了promise的原因。
2.return 非promise、非thenable(后面介绍)值。
比如 return 123。相当于调用了return Promise.resolve(123), 即返回包装了决议值为123的promise。
var p1 = Promise.resolve('p1Value')
p1.then(fullfilled => {
console.log(fullfilled) //p1Value
return 123
}, error => {
console.log(error)
})
我们看到控制台打印了
p1Value
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}
第二行打印的,就是then方法返回的promise实例。既然他返回的是promise,我们是不是可以接着对他调用then方法?我们试下
var p1 = Promise.resolve('p1Value')
p1.then(fullfilled => {
console.log(fullfilled) //p1Value
return 123
}, error => {
console.log(error)
}).then(fullfilled => {
console.log(fullfilled) //123
}, error => {
console.log(error)
})
我们在控制台会看到3行输出
p1Value
123
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}
如果理解了以上两种return 情况,应该是能理解为什么会输出这三条日志的。
3. return promise or thenable
首先说明下什么是thenable,即有then函数的对象。
var o = {
then:function(resolve) {
resolve(123)
}
}
var o = {
then: function(resolve, reject) {
reject(456)
}
}
以上两例就是thenable对象。我们发现thenable和promise对象很像。都有then方法。并且then函数 和 new Promise(function(resolve, reject))中传递的匿名函数很像。
我们看下在promise中返回thenabled对象,会是如何?
var o = {
then:function(resolve) {
resolve(123)
}
}
var p1 = Promise.resolve('p1Value')
p1.then(fullfilled => {
console.log(fullfilled) //p1Value
return o
}, error => {
console.log(error)
})
打印的日志为:
p1Value
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 123}
再来看个例子
var o = {
then:function(resolve, reject) {
reject(456)
}
}
var p1 = Promise.resolve('p1Value')
p1.then(fullfilled => {
console.log(fullfilled) //p1Value
return o
}, error => {
console.log(error)
})
打印的日志为:
p1Value
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 456}
Uncaught (in promise) 456 //先忽略这条
通过以上两个例子,我们发现当传递thenable时,promise会把thenable对象拆开,根据thenable对象内的then方法,是调用了第一个回调函数(我们以resolve命名,当然也可以是其他),还是第二个回调,来决定如何处理这个thenable对象。是不是很像调用了 return new Promise(thenable.then)的表现,有可能内部的实现就是这么处理的。
还剩下最后一种情况,return promise呢,那就直接返回啦。
then返回的 promise 情况小结
- 无return => return Promise.resolve(undefined)
- return 123 => return Promise.resolve(123)
- return thenable => return new Promise(thenable.then)
- return promise => return promise
then的默认传递
了解了调用then方法返回的promise的几种情况后,我们来看下then的默认回调,即少传或不传回调,then会如何处理。
var p1 = Promise.resolve(123)
p1.then(null, null)
//传递null或少传参数时,相当于调用了
p1.then(fullfilled => {return Promise.resolve(fullfilled)}, error => {return Promise.reject(error) })
即把then所作用的promise的决议值,作为新生成的promise的决议值,再返回这个新的promise。我们来验证下
var p1 = Promise.resolve(123)
p1.then(null, null)
.then(value => {
console.log('onResolve', value)
}, error => {
console.log('onReject', error)
})
//onResolve 123
p1.then(fullfilled => {
return Promise.resolve(fullfilled)
},error => {
return Promise.reject(error)
})
.then(value => {
console.log('onResolve', value)
}, error => {
console.log('onReject', error)
})
//onResolve 123
var p2 = Promise.reject(456)
p2.then(null, null)
.then(value => {
console.log('onResolve', value)
}, error => {
console.log('onReject', error)
})
//onReject 456
p2.then(fullfilled => {
return Promise.resolve(fullfilled)
},error => {
return Promise.reject(error)
})
.then(value => {
console.log('onResolve', value)
}, error => {
console.log('onReject', error)
})
//onReject 456
以上验证了当传递null时,then的默认行为。了解了默认行为后,我们可以简化调用方式。当只注册成功回调时,可以不传递第二个回调
p1.then(fullfilled=>{
//
})
只监听拒绝回调时,可以使用then的别名方法catch
p1.then(null, error=> {
//
})
等价于
p1.catch(error => {
//
})
promise链小结
结合以上的promise链,随手写了个涵盖以上知识点的例子。大家在终端中检验下,看输出的结果,是否都能理解。
var p1 = Promise.resolve(123)
p1.then(null, null)
.then(fullfiled=>{
console.log('onfullfiled', fullfiled)
return Promise.resolve(456)
})
.then(fullfiled=> {
console.log('onfullfiled', fullfiled)
return {
then: (resolve, reject) => {
reject(789)
}
}
})
.then(null, null)
.then(fullfiled =>{
console.log('onfullfiled', fullfiled)
}, error => {
console.log('onerror', error)
return Promise.resolve(10) //将错误处理后,又进入了正常处理流
})
.then(fullfiled=> {
console.log('onfullfiled', fullfiled)
return Promise.reject('duang duang duang error')
})
.catch(error => {
console.log('onCatch', error)
})
总结
本文首先介绍了promise的三种状态及生成不同状态promise方法,然后介绍了promise的回调处理方法then,分析了不同情况下then函数返回的promise状态。最后通过promise链,将以上知识点进行串联。希望能够对大家有所帮助。由于作者的能力有限,也正在处于学习阶段,文中不免会有错误,欢迎读者指正。