深刻理解 ES 6 中的 Promise

2,091 阅读5分钟
原文链接: eplover.github.io

故事是这样的:小铁去某厂面试,被问到Promise的出现是为了解决哪些问题?小铁胸有成竹脱口而出:Promise的出现是为提高代码的可读性,用同步的方式来写异步。然鹅,面试官对这个回答并不满意,说这只是一方面,并没有答到点上。Promise的出现是为了 …… 小铁发现自己对Promise的理解还只是停留在表面,所以有了这篇Promise知识点的总结。

小铁觉得要深刻的理解一个概念,大体可以从以下三个问题出发

  1. 是什么 ?
  2. 为什么 ?
  3. 怎么做 ?

那么,下面我们就带着这三个问题撸一撸 Promise

慢着,Promise 是个抽象的概念,直接去理解一个抽象的概念似乎并不容易。我们先搞懂为什么,再去探索它是什么会不会更容易一些呢

好,不扯闲蛋了,正式开撸!


为什么需要 Promise

// fetch 为获取接口数据的方法
const api = 'xxx'
fetch(api, function(err, data) {
  fetch(data.api, function(err, data) {
    fetch(data.api, function(err, data) {
      fetch(data.api, function(err, data) {
        // Do something with value4
      });
    });
  });
});

当我们的多个异步逻辑存在先后依赖的时候,我们需要这么写。这就是‘臭名昭著’的回调地狱。为什么叫回调地狱呢?因为这种写法会有以下几个问题

  1. 代码嵌套过多,横向扩展,可读性差。
  2. 外部无法捕获回调函数内部的错误信息。
  3. 回调函数的执行次数无法得到保证。

注:
补充说下第二点,举个🌰

  const api = 'xxx'
  try { // 这里的 try catch 无法捕获内部回调函数内部的错误信息
    fetch(api, function(error, data) {
      // 这里抛出的错误无法被最外层的 try catch 捕获
      if (error) throw new Error(error)
      try { // 这里是为了避免返回的数据 data.api 有误,导致fetch抛出异常
        fetch(data.api, function(error, data) {
          if (error) {
            handleError(error)
          } else {
            try {
              fetch(data.api, function(error, data) {
                if (error) {
                  handleError(error)
                } else {
                  // ...
                }
              })
            } catch(e) { handleError(e) }
          }
        })
      } catch(e) { handleError(e) }
    })
  } catch(e) { handleError(e) }

补充说下第三点,举个🌰

  const apis = ['xxx1', 'xxx2']
  apis.forEach(api => {
    fetch(api, function(error, data) {
      if (error) {
        handleError(error)
      } else {
        // do something
      }
    })
  })

当xxx1和xxx2对应的数据接口都挂了的时候,如下回调函数依然被调用了两次。 正确的逻辑应该是第一次fetch的时候抛出异常,然后终止程序。 但是因为回调函数内部throw的异常信息函数外部捕获不到,所以回调里一般不用throw, 所以导致错误的回调被执行了两次。

看看用Promise怎么写

const api = 'xxx'
// fixed 1,代码不再横向扩展
;(new Promise((resolve, reject) => {
  fetch(api, (error, data) => {
    error ? reject(error) : resolve(data)
  })
})).then(res => {
  return fetch(res.api, (error, data) => {
    error ? reject(error) : resolve(data)
  })
}).then(res => {
  return fetch(res.api, (error, data) => {
    if (error) {
      // 此处抛出的错误能被 catch 捕获
      throw new Error('rejected')
    } else {
      resolve(data)
    }
  })
}).then(res => {
  // ...
}).catch(error => {
  // fixed 2,统一处理错误
})

所以,Promise的出现是为了解决以上问题的。

那么 ~


什么是 Promise

A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.

以上是标准(ECMA)对Promise的定义。 通俗点说就是:Promise 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。 它允许你为异步代码执行结果的成功和失败分别绑定相应的处理方法(handlers )。

这让异步方法可以像同步方法那样返回值,但是并非立即返回执行的结果,因为毕竟执行的是异步代码,因此,它会返回一个Promise对象,如前所说,它是一个代理的对象,代理了最终返回的值,可以在后期使用

Promise有以下几种状态:

  • pending: 初始状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

promise流程示意图


那…怎么使用 Promise 呢

new Promise(
    /* executor */
    function(resolve, reject){...}
);

// pending
const api = 'xxx'
const p = new Promise(function(resolve, reject) {
  fetch(api, function(error, data) {
    error ? reject(error) : resolve(data)
  })
})

创建一个Promise对象,在executor函数体内执行异步操作 ~


Promise.prototype.then()

then() 方法返回一个 Promise,绑定处理异步代码执行的结果的函数。它最多需要有两个参数:Promise的成功和失败情况的回调函数。

语法

p.then(onFulfilled, onRejected);

p.then(function(value) {
   // fulfillment
  }, function(reason) {
  // rejection
});


Promise.prototype.catch()

catch() 方法返回一个Promise,只处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。

语法

p.catch(onRejected);

p.catch(function(reason) {
   // rejection
});


catch 和 onRejected 的区别

// 方式一
new Promise(function(resolve, reject) {
  1 ? resolve() : reject()
}).then(function() {
  console.log('resolve');
}, function(err) {
  console.log('reject');
})
// 方式二
new Promise(function(resolve, reject) {
  1 ? resolve() : reject()
}).then(function() {
  console.log('resolve');
}).catch(function(err) {
  console.log('reject');
})
// 方式三
new Promise(function(resolve, reject) {
  1 ? resolve() : reject()
}).then(function() {
  console.log('resolve');
}).then(null, function(err) {
  console.log('reject');
})

2 和 3 完全一样。1 和 2 有细微的区别,当第一个 onFulfilled 内抛出异常时 1 的 onRejected并不会被执行(二者不能同时执行),而 2 的catch回调会被执行。

Promise.prototype.then(onFulfilled, onRejected) 方法返回一个由onFulfilled/onRejected确定的Promise。

当 onFulfilled / onRejected 抛出一个错误,或者返回一个拒绝的 Promise ,then 返回一个 rejected Promise;当 onFulfilled / onRejected 返回一个 resolves Promise,或者返回任何其他值(或者没有动态的设置返回值),then 返回一个 resolved Promise。


Promise.all(iterable)

这个方法返回一个新的Promise对象,该Promise对象在iterable里所有的Promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的Promise对象失败则立即触发该Promise对象的失败。这个新的Promise对象在触发成功状态以后,会把一个包含iterable里所有Promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的Promise对象触发了失败状态,它会把iterable里第一个触发失败的Promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个Promise对象的状态集合。


Promise.race(iterable)

当iterable参数里的任意一个子Promise成功或失败后,父Promise马上也会用子Promise的成功返回值或失败详情作为参数调用父Promise绑定的相应句柄,并返回该Promise对象。


以上是小铁这次的总结 ~


最后,思考一下面 4 个 promise 之间有什么区别 ?

    doSomething().then(function () {
      return doSomethingElse();
    });
    doSomething().then(function () {
      doSomethingElse();
    });
    doSomething().then(doSomethingElse());
    doSomething().then(doSomethingElse);



参考文档:MDNECMAWe have a problem with promises


本文对你有帮助?欢迎扫码加入前端学习小组微信群: