Promise 方法及应用(then,catch,finally)

6,744 阅读4分钟

闲话少说,直接开始。

Promise.prototype.then()

理论与特点

  • 作用是为 Promise 实例添加状态改变时的回调函数

我们知道Promise主要处理的异步回调的问题,同时Promise并不直接返回最后的结果,而是将它们放在resolve或者reject里面。那我们怎么可以得到异步回调的值呢,那就要用到then方法。

  • then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。

then方法里面的两个参数虽然是可选的,但是需要注意如果Promise返回的是reject状态,我们想调用它的值,就必须设置两个参数,在第二个参数上调用reject的值。如果是resolve状态,第二个参数可以不写。

  • then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)
  • 因此可以采用链式写法,即then方法后面再调用另一个then方法。这也是Promise解决回调地狱的关键。
getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

如果采用箭头函数,上面的代码可以写得更简洁。

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("resolved: ", comments),
  err => console.log("rejected: ", err)
);

源码解析

先上源码:

function then(onFulfilled, onReject) {
    // 保存前一个promise的this
    const self = this;
    return new Promise((resolve, reject) => {
        // 封装前一个promise成功时执行的函数
        let fulfilled = () => {
                try {
                    const result = onFulfilled(self.value); // 承前
                    return result instanceof Promise ? result.then(resolve, reject) : resolve(result); //启后
                } catch (err) {
                    reject(err)
                }
            }
            // 封装前一个promise失败时执行的函数
        let rejected = () => {
            try {
                const result = onReject(self.reason);
                return result instanceof Promise ? result.then(resolve, reject) : reject(result);
            } catch (err) {
                reject(err)
            }
        }
        switch (self.status) {
            case PENDING:
                self.onFulfilledCallbacks.push(fulfilled);
                self.onRejectedCallbacks.push(rejected);
                break;
            case FULFILLED:
                fulfilled();
                break;
            case REJECT:
                rejected();
                break;
        }
    })
}

下面我们进行分析。从前一节的理论部分我们可以得到什么内容呢?

  • then方法内部包含两个参数,分别是resolved状态的回调函数和reject状态的回调函数。注意这两个参数都是可选的。可选的是什么意思,就是可以都写,可以写一个,也可以不写。但不写又对应什么状态呢?相信你已经猜到了,就是pending。这样一看then方法内部一共有三种情况。分别是pending,resolve和reject。三种情况我们会怎么设置?用if else?肯定用Switch case更加具有语义化。那我们对这三种情况分别进行分析。

    • Pending状态:其实pending状态我们无论在Promise或者then方法中都几乎用不到,但是我们还是要考虑。我们知道then方法中,resolve和reject都是可选的。但他们又是怎么可以调用呢?是不是想到Pending到resolve或reject的转换。对,就是在Pending状态下分别将resove和reject的返回值添加到对应的回调函数上。
      case PENDING:
        self.onFulfilledCallbacks.push(fulfilled);
        self.onRejectedCallbacks.push(rejected);
        break;
    
    • Resolve:直接执行:
     case FULFILLED:
        fulfilled();
        break;
    
    • Reject:直接执行:
     case REJECT:
        rejected();
        break;
    
  • then方法返回的是一个新的Promise实例

所以它的代码大致上是:

function then(onFulfilled, onReject) {
    // 保存前一个promise的this
    const self = this;
    return new Promise((resolve, reject) => {
        // 封装前一个promise成功时执行的函数
        let fulfilled = () => {
              ...
            }
        // 封装前一个promise失败时执行的函数
        let rejected = () => {
            ...
        }
        switch (self.status) {
            case PENDING:
                self.onFulfilledCallbacks.push(fulfilled);
                self.onRejectedCallbacks.push(rejected);
                break;
            case FULFILLED:
                fulfilled();
                break;
            case REJECT:
                rejected();
                break;
        }
    })
}
  • 到最后我们我们就只剩下怎么写relove和reject函数了。那怎么写呢?
  • 首先肯定要获取Promise的值。
  • 第二步对它进行处理
  • 最后处理可能报错的问题。

这三步处理下来之后,也就是then方法的实现源码了。

应用

then方法主要的用处链式调用解决回调地狱问题。

Promise.prototype.catch()

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});

上面代码中,getJSON()方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then()方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。

Promise 对象后面要跟catch()方法,这样可以处理 Promise 内部发生的错误。catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法。

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};
​
someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on

上面代码运行完catch()方法指定的回调函数,会接着运行后面那个then()方法指定的回调函数。如果没有报错,则会跳过catch()方法。

Promise.prototype.finally()

finall方法顾名思义就是最后所执行的代码。

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

引用

Promise 对象