以前只是知道有 Promise 这么一个东西,但一直不大了解,这两天看项目的源代码,有一些 Promise 的使用不是很理解,于是去学习了一些。

Promise 是什么?

用官方一些(MDN)的话来说,Promise 对象用于表示一个异步操作的最终完成 (或失败),及其结果值。
事实上,本质而言,Promise 是一个被某些函数传出的对象,我们附加回调函数(callback)使用它,而不是将回调函数传入那些函数的内部。

举一个例子

假设现在有一个函数,这个函数能异步生成音频文件create(),而它的一个回调函数是文件成功创建时候的回调,另一个则是出现异常时的回调,这是一个简单的示例:

  1. function successCallback(result){
  2. console.log("音频创建成功"+result)
  3. }
  4. function failCallback(result){
  5. console.log("音频创建失败"+result)
  6. }
  7. create(audioSettings,successCallback,failureCallback)

基于 ES6,我们可以返回一个 Promise 对象,使得可以将回调函数绑定在该 Promise 上
因此上面那个例子可以这样写:

  1. const promise = create(audioSettings);
  2. promise
  3. .then(successCallback)
  4. .catch(failureCallback)

如上面这段代码所示,我们使用了 thencatch 两个方法,将成功时的回调和失败时的回调进行了封装,使得代码更加简单易读。

理解 Promise 这个对象 —— 从 resolve 和 reject 讲起

事实上,我在看这一部分内容的时候,经常会很茫然——为什么要使用 new Promise 去创建一个呢?但是当我在实际应用的时候会发现,之所以去 new Promise 这么一个对象,最重要的是这个方法内的嵌套函数参数 —— resolvereject
我们在执行异步操作的时候,经常会遇到两面性抉择 —— 这个函数的成功与否,在当下无法判断,即,其可能成功了,而也有可能失败,但是我们在这个成功或失败之后,往往希望进行分支的操作,这就是 new Promise 的一个作用。

  1. const fined = new Promise((resolve,reject) => { // resolve 代表了成功时的回调,reject 代表 失败时的回调
  2. doAction{
  3. if(success){
  4. resolve("success")
  5. }else{
  6. reject("fail")
  7. }
  8. }
  9. })
  10. fined.then(res => {
  11. console.log(res) // "success"
  12. }).catch(error => {
  13. console.log(error) // if error -> "fail"
  14. })

特别指出的是,在这里,尽管我们定义了 fined ,但并不代表其只有在被我们调用 Promise 的时候其才会加入任务队列,实际上,在我们定义完它之后,其自然而然地就会开始运行,连带着我们的 then 和 catch。

then 和 catch

就像上面那个例子,我们在成功的时候调用了 then ,失败时调用了 reject。但是,我们也可以多加几个 then 和 catch ,达到链式(chain)编程的作用,使得代码变得扁平化;除此以外,我们也能进行嵌套 then 和 catch,限制 catch 语句的作用域,使得错误能被准确地查找出来。

  1. doSomethingCritical()
  2. .then(result => doSomethingOptional()
  3. .then(optionalResult => doSomethingExtraNice(optionalResult))
  4. .catch(e => {console.log(e.message)})) // 即使有异常也会忽略,继续运行;(最后会输出)
  5. .then(() => moreCriticalStuff())
  6. .catch(e => console.log("Critical failure: " + e.message));// 没有输出

这个内部的 catch 语句仅能捕获到 doSomethingOptional()doSomethingExtraNice() 的失败,之后就恢复到moreCriticalStuff() 的运行。
提醒:如果 doSomethingCritical() 失败,这个错误仅会被最后的(外部)catch 语句捕获到。

除此以外,假如我们先执行到了 rejectresolve,那么即使是后面有一个 setTimeout() 等的任务被添加到了任务队列,即使里面存在 其他的 reject 或 resolve,最后也不会运行相关任务,因此,对于单一的一组 then 和 catch ,其选择是单一的。

  1. const mission = new Promise((resolve,reject)=>{
  2. setTimeout(()=>{
  3. console.log("hello!")
  4. reject("oh,it has failed")
  5. },3000)
  6. resolve("haha,success!");
  7. })
  8. a.then((res)=>{
  9. console.log(res);
  10. res = "next resolve..."
  11. return res
  12. }).then(res => {
  13. console.log(res)
  14. }).catch(error => {
  15. console.log(error)
  16. })

在这个例子中,输出的结果是这样的:

  1. "haha,success!" // from line 9
  2. "next resolve..." // from line 13
  3. // after 3 seconds...
  4. "hello" // from line 3

总之,在复杂的异步调用中,我们对于 catch 和 then 的作用范围应当非常熟悉。

创建未完成的 Promise

在实际的项目应用中,我们有时候需要封装一个 Promise 对象,并给他传递值,或者是不想让这个 Promise 对象这么快就运行下去,在这个时候,创建未完成的 Promise 的作用就体现出来了。
怎么创建未完成的 Promise 呢?我们可以用函数的形式返回一个匿名 Promise 对象,就像这样:

  1. function Mission(op){
  2. return new Promise((resolve,reject) => {
  3. if(op>0.5){
  4. resolve()
  5. }else{
  6. reject()
  7. }
  8. })
  9. }
  10. let promise = Mission(Math.random())
  11. promise.then(res => {
  12. console.log("op is is bigger than 0.5")
  13. }).catch(error => {
  14. console.log("op is smaller than 0.5")
  15. })

在这个例子中,Mission 函数在创建后不会被立即调用,我们可以根据实际情况,进行异步操作,获取随机值并进行实际的处理。