Skip to content

JavaScript 中使用 Promise 处理异步任务

简介

过去,这种行为通过回调函数来处理,但是随着时间的推移,这变得难以维护,并且随着需要执行的验证和派生过程越来越多,代码缩进也变得越来越有问题。这导致了 JavaScript 中 Promise 的引入。

JavaScript Promise 是在 ECMAScript 6 中引入的,为该语言带来了更简洁的异步函数处理方式。

值得注意的是,Internet Explorer 不支持 Promise。要在 IE 中使用 Promise,需要使用 polyfill

异步函数包括文件读取、服务器请求(ajax)、等待一定时间的函数(例如 setTimeout)、可能占用大量 CPU 资源的进程(例如对信息进行哈希处理)等。

回调函数

回调函数是作为参数发送到另一个函数的函数。后者期望在一段时间后执行接收到的代码。

以我们将用作示例的 setTimeout 函数为例,它接收一个函数作为参数,该函数在以毫秒为单位定义的时间段结束后执行。

javascript
let asincrono = function (cb) {
  setTimeout(function () {
    cb('second');
  }, 3000);
};

console.log('first');
asincrono(function (result) {
  console.log(result);  
});
console.log('third');

控制台的输出结果将是:

javascript
first;
third;  
second; // 3 秒后

我们看到,直到经过一段时间(在这个例子中是 setTimeout 指定的 3000 毫秒)之后,"second" 才被打印出来。

在这里,我们使用一个名为 cb 的函数(它可以有任何名称),在异步进程完成后调用它,在这个例子中,是 3 秒的时间间隔。

Promise()

JavaScript Promise 接收一个带有两个参数(resolvereject)的函数,这两个参数都是函数,分别在进程成功执行时调用 resolve,失败时调用 reject

Promise 有三种状态:

  • Pending(等待被 resolved 或 rejected)
  • Fulfilled(已 resolved)
  • Rejected(已 rejected)

用 Promise 重写前面的例子如下所示:

javascript
// Promise 使用 'new' 实例化
let asincrono = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve('second');
  }, 3000);
});

console.log('first');
asincrono.then(function (result) {
  console.log(result);
});
console.log('third');

控制台的输出结果与前一个例子相同。

Promise 的另一个用途是能够知道异步查询何时完成,例如对服务器的查询。

javascript
let datosDeServidor = function (url) {
  return new Promise(function (resolve, reject) {
    let xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = function () {
      resolve(xhr.responseText); // 通过 'then()' 访问
    };
    xhr.onerror = function () {
      reject(xhr.statusText); // 通过 'catch()' 访问
    };
    xhr.send();
  });
};

datosDeServidor(/* url */)
  .then(function (response) {
    // 查询完成
    console.log(response); // xhr.responseText
  })
  .catch(function (error) {
    // 请求错误
    console.log(error); // xhr.statusText
  });

我们看到 then() 用于 resolvecatch() 用于 reject

JavaScript 和 NodeJs 中使用的许多库都支持 Promise。例如,用于发送 ajax 请求的 axios 就是基于 Promise 的,因此它的常规用法是:

javascript
axios({
  /* options */
})
  .then(function (response) {
    // 请求成功
    console.log(response);
  })
  .catch(function (error) {
    // 请求失败
    console.log(error);
  });

Promise.all()

Promise.all() 接收一个 Promise 数组,并在所有 Promise 都 resolved 后返回一个包含结果的数组。以查询服务器为例,如果我们有几个异步查询,想在它们都完成后再执行某些操作,就可以使用 Promise.all()

javascript
Promise.all([
  datosDeServidor(/* url from server x */),
  datosDeServidor(/* url from server y */),
  datosDeServidor(/* url from server z */),
])
  .then(function (results) {
    console.log(results[0]); // 来自服务器 x 的响应
    console.log(results[1]); // 来自服务器 y 的响应 
    console.log(results[2]); // 来自服务器 z 的响应
  })
  .catch(function (error) {
    console.log(error); // 第一个失败的 Promise 的错误
  });

JavaScript Promise 为我们处理异步代码提供了更大的灵活性。在 Web 开发中,Promise 最常见的用途之一是能够控制对 Web 服务器的请求(ajax)流,因为根据连接和其他因素,响应的确切时间是不可预测的。