2

异步常用的几种方式

setTimeout/EventEmitter/Promise/generator/async-await 今天着重来讲解一下 promise

setTimeout和Promise队列有何关联

setTimeout(function(){console.log(4)},0); 
new Promise(function(resolve){ 
    console.log(1) 
    for( var i=0 ; i<10000 ; i++ ){ i==9999 && resolve() } 
    console.log(2) })
.then(function(){ console.log(5) });
 console.log(3);

在这里我们需要先理解We have a problem with promises

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

promise 在应用的过程中需要注意的地方

1.new promise (resolve,reject){};一定要resolve,或者reject 出去不要,做副作用函数。
2.then 操作里面 接收 promise 对象,如果在promise里面是执行的纯函数,也可以返回一个 promise。resolve("常量")。避免promise 穿透

正确使用 promise 金字塔代码

新手错误 #1: promise版的金字塔问题
观察大家如何使用 PouchDB 这类大型的 promise 风格的API,我发现大量错误的 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) {

// et cetera...
是的,实际上你可以像使用回调一样使用 promises,恩,就像用打磨机去削脚趾甲一样,你确实可以这么做。

并且如果你以为这样的错误只限于初学者,那么你会惊讶于我实际上是在黑莓官方开发者博客上看到上面的代码。老的回调风格的习惯难以消灭。(至开发者: 抱歉选了你的例子,但是你的例子将会有积极的教育意义)

正确的风格应该是这样:

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);
});

这种写法被称为 composing promises ,是 promises 的强大能力之一。每一个函数只会在前一个 promise 被调用并且完成回调后调用,并且这个函数会被前一个 promise 的输出调用,稍后我们在这块做更多的讨论。

  • 4.promise 中foreach 操作,转换成 promise.all()=>返回一个数组
  • 5.在任何应用到了promise的过程中,一定要注意使用catch 将异常抛出来,不然会堵塞整个加载

使用副作用调用而非返回

下面的代码有什么问题?

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

好了,现在是时候讨论一下关于 promises 你所需要知道的一切。

认真的说,这是一个一旦你理解了它,就会避免所有我提及的错误的古怪的技巧。你准备好了么?

就如我前面所说,promises 的奇妙在于给予我们以前的 return 与 throw。但是在实践中这到底是怎么一回事呢?

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

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

我们可以做什么呢?有三种事情:

return 另一个 promise
return 一个同步的值 (或者 undefined)
throw 一个同步异常
就是这样。一旦你理解了这个技巧,你就理解了 promises。因此让我们逐个了解下。

返回另一个 promise
这是一个在 promise 文档中常见的使用模式,也就是我们在上文中提到的 “composing promises”:

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

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

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

抛出同步异常
谈到 throw,这是让 promises 更加赞的一点。比如我们希望在用户已经登出时,抛出一个同步异常。这会非常简单:

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 中出现异步异常,他也会接收到。再强调一次,这个函数并不需要关心这个异常是同步还是异步返回的。

这种特性非常有用,因此它能够在开发过程中帮助定位代码问题。举例来说,如果在 then() 函数内部中的任何地方,我们执行 JSON.parse(),如果 JSON 格式是错误的,那么它就会抛出一个异常。如果是使用回调风格,这个错误很可能就会被吃掉,但是使用 promises,我们可以轻易的在 catch() 函数中处理它了。

一定要注意我们在then 函数内部的时候

我们可以做什么呢?有三种事情:

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

参考资料


木子喵
492 声望26 粉丝

too young, too naive