手记

ES6异步实现的三种方式:Callback/Promise/Async&Await

1.利用callback函数

回调函数,可以理解成作为参数传递的函数对象。

什么是callback函数

下面举个使用回调函数的栗子:

$("#myDiv").on("click", function () {
    this.innerHTML = "you clicked me!";
});

这个栗子中,我们使用 jQuery 给一个 id 为 myDiv 的元素绑定了 click 事件的处理程序,这里的事件处理程序就是一个回调函数。当然回调函数并非是一种特殊类型的对象,其实就是普通的函数,但是被作为其他函数的参数传递,在某个时刻才会被选择性地使用。

实际上,这是由 jQuery 提供的机制,在事件处理程序被调用时,this 会指向事件的目标对象,这里也就是被点击的 myDiv 元素,而 HTML 元素有 innerHTML 属性,就是上面的使用方式了。这是怎么实现的呢,合理揣测下的话,肯定是这个回调函数在调用是被显式指定了“上下文对象” this,有可能就是通过 .call() 或 .apply() 的方式。

事实上,这个代码是同步的。如果在JQuery的年代想要实现异步操作需要借助ajax来实现。

通过callback函数来实现异步的栗子:

function getCustomer(id, callback) {
  setTimeout(() => {
    callback({ 
      id: 1, 
      name: 'Mosh Hamedani', 
      isGold: true, 
      email: 'email' 
    });
  }, 4000);  
}

总结:由于写法需要一层层嵌套,容易引起callback hell问题,因此,在处理异步调用时不建议使用此方法。

2.利用promise对象来实现

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

将上面的栗子代码改成利用promise对象实现如下:

function getCustomer(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ 
        id: 1, 
        name: 'Mosh Hamedani', 
        isGold: true, 
        email: 'email' 
      });
    }, 4000);  
  });
}

总结:缺点:不能用try/catch

3.通过async&await语法糖

在主体函数之前使用了async关键字。在函数体内,使用了await关键字。当然await关键字只能出现在用async声明的函数体内。该函数会隐式地返回一个Promise对象,函数体内的return值,将会作为这个Promise对象resolve时的参数。
可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

// before
getCustomer(1, (customer) => {
  console.log('Customer: ', customer);
  if (customer.isGold) {
    getTopMovies((movies) => {
      console.log('Top movies: ', movies);
      sendEmail(customer.email, movies, () => {
        console.log('Email sent...')
      });
    });
  }
});

// after
async function notifyCustomer() {
  const customer = await getCustomer(1);
  console.log('Customer: ', customer);
  if (customer.isGold) {
    const movies = await getTopMovies();
    console.log('Top movies: ', movies);
    await sendEmail(customer.email, movies);
    console.log('Email sent...');
  }
}
notifyCustomer();

function getCustomer(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ 
        id: 1, 
        name: 'Mosh Hamedani', 
        isGold: true, 
        email: 'email' 
      });
    }, 4000);  
  });
}

function getTopMovies() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(['movie1', 'movie2']);
    }, 4000);
  });
}

function sendEmail(email, movies) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, 4000);
  });
}

总结:

简约而干净Concise and clean

我们看一下上面两处代码的代码量,就可以直观地看出使用Async/await对于代码量的节省是很明显的。对比Promise,我们不需要书写.then,不需要新建一个匿名函数处理响应,也不需要再把数据赋值给一个我们其实并不需要的变量。同样,我们避免了耦合的出现。这些看似很小的优势其实是很直观的,在下面的代码示例中,将会更加放大。

错误处理Error handling

Async/await使得处理同步+异步错误成为了现实。我们同样使用try/catch结构,但是在promises的情况下,try/catch难以处理在JSON.parse过程中的问题,原因是这个错误发生在Promise内部。想要处理这种情况下的错误,我们只能再嵌套一层try/catch。
Async/await是近些年来JavaScript最具革命性的新特性之一,提供了代替Promise的方案。

4人推荐
随时随地看视频
慕课网APP