JS异步编程之Promise详解和使用总结

8,965 阅读6分钟

前言

Javascript异步编程可以算是JS的难点之一。下面就异步编程方法之一的Promise进行详细介绍和总结。但说到Promise之前,我会简单提一下什么是JS异步和回调函数。

JS异步

JS异步是指在进行某些需要耗时不会立即返回结果的操作时,不会阻塞后面的操作,一旦该耗时的操作完成时,则会通知需要调用其结果的函数来做后续处理。这是一种异步非阻塞的操作,也就是说任务的排列顺序和执行任务是不一致的。

回调函数

和同步操作不同,异步操作即不会立即返回结果的操作(如发起网络请求,下载文件,操作数据库等)。如果我们后续的函数需要之前返回的结果,又怎样使之前的异步操作在其完成时通知到后续函数来执行呢?

通常,我们可以将这个函数先定义,存储在内存中,将其当做参数传入之前的异步操作函数中,等异步操作结束,就会调用执行这个函数,这个函数就叫做回调函数(callback)。

如果不用callback,由于js会立即执行后面console.log,导致打印出来的photoundefined

var photo = downloadPhoto('http://coolcats.com/cat.gif')
console.log(photo)  //undefined 

使用callback时,我们将handlePhoto当做callback传入downloadPhoto这个异步函数中,那么当图片下载行为结束后,无论是成功还是失败,都会执行到handlePhoto,对photo或者是error进行处理。

downloadPhoto('http://coolcats.com/cat.gif', handlePhoto)
function handlePhoto (error, photo) {
  if (error) console.error('Download error!', error)
  else console.log('Download finished', photo)
}
console.log('Download started')

那假如callback函数同样是个异步函数,且callback里又嵌入了callback呢? 如此一来,嵌套太深容易引发“回调地狱”,即代码只会横向发展,不好管理。

为此,对于那些需要连续执行的异步操作,Promise可以是一种很好的解决办法。

Promise

Promise的概念对于初学者来说一直很抽象,我们可以举个例子: 比如你是个经销商,你要去工厂订货,拿到货后你才能自己销售。那么你和工厂之前立下一个契约,保证工厂在在完成生产后通知你,或者就算是因某种原因出错了而无法生产也会通知到你。那么此时这里的契约就相当于我们要讲述的promise,promise就像是个特殊的对象,连接了工厂的生产行为和你的消费行为,是生产者和消费者间的纽带。

Promise的创建

Promise创建时,会传给promise一个称为excutor执行器的函数。这个excutor我们可以理解为生产者的生产过程函数。这个函数含有两个参数resolvereject,这俩参数也同样是函数,用来传递异步操作的结果。语法如下:

let promise = new Promise(function(resolve, reject) {
  // executor 
})

有几点值得说一下:

  1. 在promise对象创建时,excutor会立即执行。
  2. resolvereject是JS引擎自动创建的函数,我们无需自己创建,只需将其作为参数传入就好。
  3. 创建的promise的内部状态是个对象,初始时为:
{
   state,  //pending
   result,  //undefined
}

一旦exucutor执行完,要么产生value,要么产生error,此时会立即调用resolve(当产生value时)或者调用reject(当产生error)时,内部状态也会随之改变,如下图所示:

注意,当excutor里面即使调用了多个resolvereject,其最终还是只执行一个,其他的都被忽略掉。

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); // ignored
  setTimeout(() => resolve("…")); // ignored
});

Promise的then,catch和finally

在上面例子中,既然生产者的行为完成了,结果也传递出去了,那么如何通知消费者呢?我们可以使用.then, .catch, 和.finally来注册消费者的函数,把.then.catch.finally看做是个订阅列表,将消费者的函数注册于此,一旦收到结果时,就可以通知到对方进行相应的处理。

  • .then的用法如下:
promise.then(
result = > resultHandle(result)
error = > errorHandle(error)
)

.then()接收2个函数,一个用来处理正常结果result,一个用来处理error,但一般情况下,我们也可以不用在.then()中传入这个error处理的函数。

  • .catch的用法如下:
promise.catch(
    error = > errorHandle(error)
)

其实这就是相当于promise.then(null, errorHandle).catch会捕获到整个异步操作中,或者是一系列连续的异步操作链中的出现的任何类型的错误,一旦抛出错误,则会直接转入到.catch中进行错误处理。

  • .finally的用法如下:
promise.finally(
    finalHandle
)

当promise的状态确定时,即无论拿到的是正常结果还是错误信息,总会执行这个finalHandle函数。用.finally可以做一些清理操作,比如发起网络请求后,可以停止loading显示。

Promise链式调用

当有一系列的异步操作需要一个接一个执行时,可以使用promise的调用链。 举个例子,我们用fetch这个方法去发起网络请求,fetch()返回的是一个promise对象,那么我们可以对其连续地调用.then来进行一步步连续地异步操作。注意:promise.then()返回的是一个新的promise对象,所以我们才可以继续对其调用.then

// Make a request for user.json
fetch('/article/promise-chaining/user.json')
  // Load it as json
  .then(response => response.json())
  // Make a request to github
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  // Load the response as json
  .then(response => response.json())
  // Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it)
  .then(githubUser => {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => img.remove(), 3000); // (*)
  }).catch(error => console.log(error));

这段代码的作用就是先发起网络请求获取到服务端的相应内容(其实只是响应头),然后通过调用response.json()继续获取response完整的远程数据并将其解析为JSON格式(也是异步操作),接着根据json中user的name信息,继续发起网络请求,拿到用户object及其头像url,展示其头像并在3秒后删除头像图片。.catch会处理上面一系列流程中出现的任何错误。

值得注意的是,promise.then( handleFunction ) 中的handleFunction可以返回立即值,也可以返回promise对象。如果返回立即值,则可以直接把结果传入到下一步的.then进行处理,但是如果返回的是promise对象,那么一定要等到这个返回的promise处理完,拿到结果后,才会进行下一步的.then处理!可以用下图加以理解:

总结

在JS异步编程中,Promise相对于callback,具有更优的代码流,并且具有很好的灵活性。Promise符合自然的事物执行顺序,即先做异步操作,然后再用.then告知下一步该做什么。而在Callback的用法中,先得知道下一步做什么,然后才能将其作为callback函数传入异步操作函数中。而且,promise在得到结果后,可以通知到多个后续的结果处理函数,.then就像一个订阅列表一样。而在callback的用法中,只能传入1个callback函数。

参考链接

Promise: javascript.info/promise-bas…

Promise MDN文档: developer.mozilla.org/en-US/docs/…

Callback Hell:callbackhell.com/

Fetch: developer.mozilla.org/en-US/docs/…