promise新手常见问题

导读

上周讲了promise用法,这周我们讲一下promise实战中可能出现得一些易错点,如果对promise基础不是很了解可以去看我的上一篇文章 promise入门

常见问题一 :回调地狱式用法
remotedb.allDocs({
  include_docs: true,
  attachments: true
}).then(function (result) {
  var docs = result.rows;
  docs.forEach(function(element) {
    localdb.put(element.doc).then(function(response) {
      alert("Pulled doc with id " + element.doc._id + " and added to local db.");
    }).catch(function (err) {
      if (err.status == 409) {
        localdb.get(element.doc._id).then(function (resp) {
          localdb.remove(resp._id, resp._rev).then(function (resp) {}
    }
  })

promise得新特性之一就是解决回调地狱问题,所以我们可以换个更优雅得写法达到上段代码的效果

remotedb.allDocs(...).then(function (resultOfAllDocs) {
  return localdb.put(...);
}).then(function (resultOfPut) {
  return localdb.get(...);
}).then(function (resultOfGet) {
  return localdb.put(...);
}).catch(function (err) {
  console.log(err);
});
常见问题二 : 用了 promises 后怎么用 forEach?
/ I want to remove() all docs
db.allDocs({include_docs: true}).then(function (result) {
  result.rows.forEach(function (row) {
    db.remove(row.doc);  
  });
}).then(function () {
  // I naively believe all docs have been removed() now!
});

这份代码有什么问题?问题在于第一个then函数实际上返回的是 undefined(入门篇讲过我们需要手动return),这意味着第二个方法不会等待所有 documents 都执行 db.remove()方法(因为后一个then接收到的undefind并没有类似于promise实例状态ejected/fullfilled)。所以他不会等待任何事情,并且可能会在任意数量的文档被删除后执行!
这里我们需要用 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 都成功返回后才返回。一旦数组中的 promise任意一个返回错误,Promise.all() 也会返回错误,他是异步版的 for循环。

常见问题三 : 忘记使用 .catch()

我们在使用程序的时候,难免会出现问题,如果不适用catch找起问题来会特别麻烦

somePromise().then(function () {
  return anotherPromise();
}).then(function () {
  return yetAnotherPromise();
}).catch(
err=>{
  thorw new Error(err)
}); 
常见问题四 :使用副作用调用而非返回

没有return返回新的promise就会默认返回一个undefind,这就是副作用

somePromise().then(function () {
  someOtherPromise();
}).then(function () {
  // Gee, I hope someOtherPromise() has resolved!
  // Spoiler alert: it hasn't.
});

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

somePromise().then(function () {
  // 这里可以做什么???
});

我们可以在这里做三件事情

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

我们来看一下这三种事情

  • 返回另一个 promise
getUserByName('nolan').then(function (user) {
  return getUserAccountById(user.id);
}).then(function (userAccount) {
  // I got a user account!
});

注意到我是 return 第二个 promise,这个 return 非常重要。如果我没有写 returngetUserAccountById() 就会成为一个副作用,并且下一个函数将会接收到 undefined 而非新的 promise

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

返回 undefined 通常是错误的,但是返回一个同步值实际上是将同步代码包裹为 promise 风格代码的一种非常赞的手段。

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 是从同步方法还是异步方法中获取的,并且第一个函数可以非常自由的返回一个同步或者异步值。

不幸的是,有一个不便的现实是在 JavaScript 中无返回值函数在技术上是返回 undefined,这就意味着当你本意是返回某些值时,你很容易会不经意间引入副作用。

出于这个原因,我个人养成了在 then() 函数内部 永远返回或抛出 的习惯。我建议你也这样做。

进阶错误

不知道 Promise.resolve()/Promise.reject();

我们会经常这么使用promise

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

使用 Promise.resolve()会更加简洁

Promise.resolve(someSynchronousValue).then(/* ... */);

我们应该在所有 promise 形式的 API 接口中这样使用它

function somePromiseAPI() {
  return Promise.resolve().then(function () {
    doSomethingThatMayThrow();
    return 'foo';
  }).then(/* ... */);
}

任何有可能 throw 同步异常的代码都是一个后续会导致几乎无法调试异常的潜在因素。但是如果你将所有代码都使用 Promise.resolve() 封装,那么你总是可以在之后使用 catch() 来捕获它。
类似的,还有 Promise.reject() 你可以用来返回一个立刻返回失败的 promise。

Promise.reject(new Error('some awful error'));
promises 穿透
Promise.resolve('foo').then(
  Promise.resolve('bar')
  ).then(function (result) {
  console.log(result); //foo
});

之所以会打印foo而不是bar,是因为我们 Promise.resolve('bar')这段代码有问题,这段代码返回的是一个promise,但我们并没有return

Promise.resolve('foo').then(function () {
  return Promise.resolve('bar');
}).then(function (result) {
  console.log(result);
});

当然promise还有一些高级的用法,大家可以去读一下 promise,我的实例代码全部是这篇文章的,作者是一个外国大牛,有兴趣的可以去看一下

推荐阅读更多精彩内容

  • 本文适用的读者 本文写给有一定Promise使用经验的人,如果你还没有使用过Promise,这篇文章可能不适合你,...
    HZ充电大喵阅读 3,922评论 5 19
  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 4,024评论 2 37
  • 你不知道JS:异步 第三章:Promises 在第二章,我们指出了采用回调来表达异步和管理并发时的两种主要不足:缺...
    purple_force阅读 563评论 1 3
  • 英文:Nolan Lawson 译文:伯乐在线专栏作者 - abell123如有好文章投稿,请点击 → 这里了解...
    北方蜘蛛阅读 3,713评论 1 10
  • 本文作者就是我,简书的microkof。如果您觉得本文对您的工作有意义,产生了不可估量的价值,那么请您不吝打赏我,...
    microkof阅读 11,478评论 8 32