Promise\async\await 的简单学习笔记

1,007 阅读4分钟

随便查查网上的 Promise 教程,多到数不胜数,然而我还是没有准确理解什么是 Promise 以及它的使用场景。所以打算自己写一下学习心得,用来查漏补缺。目标是写的简单直白,便于以后查阅反思。

什么是 Promise?

“Promise 是一个对象。” 对于这种解释,我不是很满意,毕竟在 JS 的世界中,万物皆可对象(这句话也是流传已久的bug,想知道具体的原因,请查阅 null 相关的知识)。我们通过 typeof Promise 可以得到 function。这进一步说明,Promise 更是一个函数。同时来复习一下:JS 中,函数是一种特殊的对象,MDN 中也叫做头等对象(first-class)。所以,请不要简单解释为 Promise 是一个对象,这有点偷懒。

那么在认识到 Promise 是一个函数后,这个函数的本质,又是什么?答案是:构造函数。为什么这样理解?因为我们在使用 Promise 的时候,永远是 new Promise() 开始的。既然是构造函数,当我们创建一个 Promise 实例的时候,这个实例对象都有什么内容?来看看下面的代码:

var p = new Promise(function(resolve, reject){});
consoel.log(p);
// __proto__: Promise
// [[PromiseStatus]]: "pending"
// [[PromiseValue]]: undefined

console.log(p.__proto__)
// Promise {constructor: ƒ, then: ƒ, catch: ƒ, finally: ƒ, Symbol(Symbol.toStringTag): "Promise"}

我们得到的对象 p 上,这个对象也比较简单,一共只有三个东西:proto,PromiseStatus,PromiseValue。这三个值。__proto__ 上则存在我们熟悉的 then 等方法,这就是链式调用的原理——通过返回 Promise 对象,调用其原型链上的方法实现。其余两个是内部变量,一个是记录内部状态,另一个记录了返回值。

Promise 的状态有三种:

  • pending
  • fulfilled/resolved (好讨厌这种名不正言不顺的,一种状态两种表示的东西)
  • rejected

PromiseValue 记录的返回值也很简单。如果一个 promise 的状态是完成(resolved)那么,返回值就是 then(res => return res;) 里的 res;如果是拒绝状态,那么则是 .catch() 里面的。值得一提的是,Promise 实例中的状态,永远是确定的,而且不可逆,也就是说一个已经 resolved 或者 rejected 的promise,不能转化成 pending。

为什么要有 Promise?

之前我们说到 Promise 是一个构造函数,是从 Promise 的类型来看问题。如果从应用层面来看待,那么 Promise 则是一套异步操作的处理机制。这种机制十分擅长解决回调地狱。来写一个在 jQuery 时代不可避免的回调场景:

$.ajax({
  success: function(res) {
    if(res) {
      callAnotherFun(function(res) {
        $.ajax({
          success: secondResponse
        })
      });
    }
  }
})

改写成 Promise

function fetch() {
  return new Promise(function(resolve) {
    $.ajax({
      success: function(res) {
        resolve(res);
      }
    })
  });
}

fetch().then(res => {
  return callAnotherFun(function(res) {
    return fetch()
  })
}).then(res => {
  secondResponse(res)
})

从上面的改写可以看出 Promise 将多层函数回调的嵌套

function a() {
  b(function () {
    c(function() {
      d(function() {
        ...
      })
    })
  })
}

改写成了

myPromise().then(res => {
  a();
  return myPromise();
}).then(res => {
  b();
  return myPromise();
}).then(res => {
  c();
  return myPromise();
}).then(red => {
  d();
})

这种写法,统一了异步处理的风格,已经写入 ES6 的标准之中。其实关于这样的链式调用,我们仍然有优化的空间,这个优化操作就引入下面要讲的 async/await 的相关内容。

async\await 是什么?

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

我们可以将上一节的代码改写成 async/await 的形式,如下:

async function myPromise() { }
async function main() {
  await myPromise(function() {a () });
  await myPromise(function() {b () });
  await myPromise(function() {c () });
  await myPromise(function() {d () });
}

main();

从上图可以看出,async 提供了一种像同步风格一样来编写异步过程的代码的方式。这里要注意的是 await 一定是写在 async 里面的。顾名思义它是 async wait异步等待的意思。

异步的使用场景?

那么结合到具体业务,我们应该如何使用。常见的异步场景有:

  • 文件上传
  • 图片加载
  • 自动补全
  • 用户事件

等等…… 下面我从一个小栗子来简单应用下,感受 Promise 和 async 结合的代码的可读性。

一个红绿灯的例子

参考资料

pic