你真的彻底掌握了promise么?

最近在整理js中异步编程方法时,回顾了一下promise,又发现了一些遗漏的重要知识点,好比promise.resolve()传递不一样参数的含义?好比当一个promise依赖另外一个promise时事件执行顺序?好比当catch捕获到了错误后,会不会继续执行后面的then方法?下文将对这些问题一一解答,并再次强调一些重要的知识点。es6

1.promise语法

Promise编程的核心思想是若是数据就绪(promised),那么(then)作点什么。编程

下文是一个promise实例。json

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
复制代码

Promise构造函数接受一个函数做为参数,该函数的两个参数分别是resolve和reject。数组

resolve函数的做用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为resolved),在异步操做成功时调用,并将异步操做的结果,做为参数传递出去;promise

reject函数的做用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为rejected), 在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。缓存

Promise实例生成之后,能够用then方法分别指定resolved状态和rejected状态的回调函数。bash

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
复制代码

then方法能够接受两个回调函数做为参数。hexo

第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不必定要提供。这两个函数都接受Promise对象传出的值做为参数。异步

下面是一个使用then的例子。then方法返回的是一个新的Promise实例。 所以能够采用链式写法,即then方法后面再调用另外一个then方法。ide

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

复制代码

上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成之后,会将返回结果做为参数,传入第二个回调函数。

Promise.prototype.catch方法用于指定发生错误时的回调函数。

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

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

通常老是建议,Promise 对象后面要跟catch方法,这样能够处理 Promise 内部发生的错误。catch方法返回的仍是一个 Promise 对象,所以后面还能够接着调用then方法。

注意:

1.若是调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。resolve函数的参数除了正常的值之外,还多是另外一个 Promise 实例。

const p1 = new Promise(function (resolve, reject) {
  // ...
});

const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})
复制代码

上面代码中,p1和p2都是 Promise 的实例,可是p2的resolve方法将p1做为参数,即一个异步操做的结果是返回另外一个异步操做。

这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。若是p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;若是p1的状态已是resolved或者rejected,那么p2的回调函数将会马上执行。

2.调用resolve或reject并不会终结 Promise 的参数函数的执行。

3.then方法是定义在原型对象Promise.prototype上的。

4.若是没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

5.Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。由于 Promise的状态一旦改变,就永久保持该状态,不会再变了。

2. Promise.resolve()

有时须要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个做用。

Promise.resolve等价于下面的写法。

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
复制代码

Promise.resolve方法的参数分红四种状况。

1)参数是一个 Promise 实例

若是参数是 Promise 实例,那么Promise.resolve将不作任何修改、原封不动地返回这个实例。

2)参数是一个thenable对象

thenable对象指的是具备then方法的对象,好比下面这个对象。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};
复制代码

Promise.resolve方法会将这个对象转为 Promise 对象,而后就当即执行thenable对象的then方法。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});
复制代码

上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而当即执行最后那个then方法指定的回调函数,输出 42。

3)参数不是具备then方法的对象,或根本就不是对象。

若是参数是一个原始值,或者是一个不具备then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。

const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hello
复制代码

上面代码生成一个新的 Promise 对象的实例p。因为字符串Hello不属于异步操做(判断方法是字符串对象不具备 then 方法),返回 Promise 实例的状态从一辈子成就是resolved,因此回调函数会当即执行。Promise.resolve方法的参数,会同时传给回调函数。

4)不带有任何参数

Promise.resolve方法容许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

因此,若是但愿获得一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法。

const p = Promise.resolve();

p.then(function () {
  // ...
});
复制代码

上面代码的变量p就是一个 Promise 对象。须要注意的是,当即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three
复制代码

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise. resolve()在本轮“事件循环”结束时执行,console.log('one')则是当即执行,所以最早输出。

3.新手错误

1)用了 promises 后怎么用 forEach?

// 我想删除全部的docs
db.allDocs({include_docs: true}).then(function (result) {
  result.rows.forEach(function (row) {
    db.remove(row.doc);  
  });
}).then(function () {
  // 我天真的觉得全部的docs都被删除了!
});
复制代码

问题在于第一个函数实际上返回的是 undefined,这意味着第二个方法不会等待全部 documents 都执行 db.remove()。实际上他不会等待任何事情,而且可能会在任意数量的文档被删除后执行!

简而言之,forEach()/for/while 并不是你寻找的解决方案。你须要的是 Promise.all():

db.allDocs({include_docs: true}).then(function (result) {
  return Promise.all(result.rows.map(function (row) {
    return db.remove(row.doc);
  }));
}).then(function (arrayOfResults) {
  // All docs have really been removed() now!
});
复制代码

上面的代码是什么意思呢?大致来讲,Promise.all()会以一个 promises 数组为输入,而且返回一个新的 promise。这个新的 promise 会在数组中全部的 promises 都成功返回后才返回。他是异步版的 for 循环。

而且 Promise.all() 会将执行结果组成的数组返回到下一个函数,好比当你但愿从 PouchDB 中获取多个对象时,会很是有用。此外一个更加有用的特效是,一旦数组中的 promise 任意一个返回错误,Promise.all() 也会返回错误。

2)忘记使用catch

单纯的坚信本身的 promises 会永远不出现异常,不少开发者会忘记在他们的代码中添加一个 .catch()。然而不幸的是这也意味着,任何被抛出的异常都会被吃掉,而且你没法在 console 中观察到他们。这类问题 debug 起来会很是痛苦。

3) 使用反作用调用而非返回

下面的代码有什么问题?

somePromise().then(function () {
  someOtherPromise();
}).then(function () {
  // 我但愿someOtherPromise() 状态变成resolved!
  // 可是并无
});
复制代码

每个 promise 都会提供给你一个 then() 函数 (或是 catch(),实际上只是 then(null, ...) 的语法糖)。当咱们在 then() 函数内部时:

somePromise().then(function () {
  // I'm inside a then() function! }); 复制代码

咱们能够作什么呢?有三种事情:

  • return 另外一个 promise
  • return 一个同步的值 (或者 undefined)
  • throw 一个同步异常

就是这样。一旦你理解了这个技巧,你就理解了 promises。

所以让咱们逐个了解下。

返回另外一个 promise

getUserByName('nolan').then(function (user) {
  return getUserAccountById(user.id);
}).then(function (userAccount) {
  // I got a user account!
});
复制代码

注意:我是 return 第二个 promise,这个 return 很是重要。若是我没有写 returngetUserAccountById() 就会成为一个反作用,而且下一个函数将会接收到 undefined 而非 userAccount

返回一个同步值 (或者 undefined)

返回 undefined 一般是错误的,可是返回一个同步值其实是将同步代码包裹为 promise 风格代码的一种很是赞的手段。举例来讲,咱们对 users 信息有一个内存缓存。咱们能够这样作:

getUserByName('nolan').then(function (user) {
  if (inMemoryCache[user.id]) {
    return inMemoryCache[user.id];    // returning a synchronous value!
  }
  return getUserAccountById(user.id); // returning a promise!
}).then(function (userAccount) {
  // I got a user account!
});

复制代码

第二个函数不须要关心 userAccount 是从同步方法仍是异步方法中获取的,而且第一个函数能够很是自由的返回一个同步或者异步值。

抛出同步异常

好比咱们但愿在用户已经登出时,抛出一个同步异常。这会很是简单:

getUserByName('nolan').then(function (user) {
  if (user.isLoggedOut()) {
    throw new Error('user logged out!'); // throwing a synchronous error!
  }
  if (inMemoryCache[user.id]) {
    return inMemoryCache[user.id];       // returning a synchronous value!
  }
  return getUserAccountById(user.id);    // returning a promise!
}).then(function (userAccount) {
  // I got a user account!
}).catch(function (err) {
  // Boo, I got an error!
});
复制代码

若是用户已经登出,咱们的 catch() 会接收到一个同步异常,而且若是 后续的 promise 中出现异步异常,他也会接收到。再强调一次,这个函数并不须要关心这个异常是同步仍是异步返回的。

4.参考网址

http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/

http://es6.ruanyifeng.com/#docs/promise

原文见个人博客

相关文章
相关标签/搜索