以前只是知道有 Promise 这么一个东西,但一直不大了解,这两天看项目的源代码,有一些 Promise 的使用不是很理解,于是去学习了一些。
Promise 是什么?
用官方一些(MDN)的话来说,Promise
对象用于表示一个异步操作的最终完成 (或失败),及其结果值。
事实上,本质而言,Promise 是一个被某些函数传出的对象,我们附加回调函数(callback)使用它,而不是将回调函数传入那些函数的内部。
举一个例子
假设现在有一个函数,这个函数能异步生成音频文件create()
,而它的一个回调函数是文件成功创建时候的回调,另一个则是出现异常时的回调,这是一个简单的示例:
function successCallback(result){
console.log("音频创建成功"+result)
}
function failCallback(result){
console.log("音频创建失败"+result)
}
create(audioSettings,successCallback,failureCallback)
基于 ES6,我们可以返回一个 Promise 对象,使得可以将回调函数绑定在该 Promise 上
因此上面那个例子可以这样写:
const promise = create(audioSettings);
promise
.then(successCallback)
.catch(failureCallback)
如上面这段代码所示,我们使用了 then
和 catch
两个方法,将成功时的回调和失败时的回调进行了封装,使得代码更加简单易读。
理解 Promise 这个对象 —— 从 resolve 和 reject 讲起
事实上,我在看这一部分内容的时候,经常会很茫然——为什么要使用 new Promise
去创建一个呢?但是当我在实际应用的时候会发现,之所以去 new Promise 这么一个对象,最重要的是这个方法内的嵌套函数参数 —— resolve
和 reject
。
我们在执行异步操作的时候,经常会遇到两面性抉择 —— 这个函数的成功与否,在当下无法判断,即,其可能成功了,而也有可能失败,但是我们在这个成功或失败之后,往往希望进行分支的操作,这就是 new Promise 的一个作用。
const fined = new Promise((resolve,reject) => { // resolve 代表了成功时的回调,reject 代表 失败时的回调
doAction{
if(success){
resolve("success")
}else{
reject("fail")
}
}
})
fined.then(res => {
console.log(res) // "success"
}).catch(error => {
console.log(error) // if error -> "fail"
})
特别指出的是,在这里,尽管我们定义了 fined ,但并不代表其只有在被我们调用 Promise 的时候其才会加入任务队列,实际上,在我们定义完它之后,其自然而然地就会开始运行,连带着我们的 then 和 catch。
then 和 catch
就像上面那个例子,我们在成功的时候调用了 then ,失败时调用了 reject。但是,我们也可以多加几个 then 和 catch ,达到链式(chain)编程的作用,使得代码变得扁平化;除此以外,我们也能进行嵌套 then 和 catch,限制 catch 语句的作用域,使得错误能被准确地查找出来。
doSomethingCritical()
.then(result => doSomethingOptional()
.then(optionalResult => doSomethingExtraNice(optionalResult))
.catch(e => {console.log(e.message)})) // 即使有异常也会忽略,继续运行;(最后会输出)
.then(() => moreCriticalStuff())
.catch(e => console.log("Critical failure: " + e.message));// 没有输出
这个内部的 catch 语句仅能捕获到 doSomethingOptional()
和 doSomethingExtraNice()
的失败,之后就恢复到moreCriticalStuff()
的运行。
提醒:如果 doSomethingCritical()
失败,这个错误仅会被最后的(外部)catch
语句捕获到。
除此以外,假如我们先执行到了 reject
或 resolve
,那么即使是后面有一个 setTimeout() 等的任务被添加到了任务队列,即使里面存在 其他的 reject 或 resolve,最后也不会运行相关任务,因此,对于单一的一组 then 和 catch ,其选择是单一的。
const mission = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("hello!")
reject("oh,it has failed")
},3000)
resolve("haha,success!");
})
a.then((res)=>{
console.log(res);
res = "next resolve..."
return res
}).then(res => {
console.log(res)
}).catch(error => {
console.log(error)
})
在这个例子中,输出的结果是这样的:
"haha,success!" // from line 9
"next resolve..." // from line 13
// after 3 seconds...
"hello" // from line 3
总之,在复杂的异步调用中,我们对于 catch 和 then 的作用范围应当非常熟悉。
创建未完成的 Promise
在实际的项目应用中,我们有时候需要封装一个 Promise 对象,并给他传递值,或者是不想让这个 Promise 对象这么快就运行下去,在这个时候,创建未完成的 Promise 的作用就体现出来了。
怎么创建未完成的 Promise 呢?我们可以用函数的形式返回一个匿名 Promise 对象,就像这样:
function Mission(op){
return new Promise((resolve,reject) => {
if(op>0.5){
resolve()
}else{
reject()
}
})
}
let promise = Mission(Math.random())
promise.then(res => {
console.log("op is is bigger than 0.5")
}).catch(error => {
console.log("op is smaller than 0.5")
})
在这个例子中,Mission 函数在创建后不会被立即调用,我们可以根据实际情况,进行异步操作,获取随机值并进行实际的处理。