--- title: 选择正确的方法 slug: learn/JavaScript/异步/Choosing_the_right_approach translation_of: Learn/JavaScript/Asynchronous/Choosing_the_right_approach ---
{{LearnSidebar}}
{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}

为了完成这个模块,我们将简要讨论之前章节谈论过编码技术和功能,看看你应该使用哪一个,并提供适当的建议和提醒。随着时间的推移,我们可能会添加到此资源中。

预备条件: 基本的计算机素养,对JavaScript基础知识的合理理解。
目标: 能够在使用不同的异步编程技术时做出合理的选择。

异步回调

通常在旧式API中找到,涉及将函数作为参数传递给另一个函数,然后在异步操作完成时调用该函数,以便回调可以依次对结果执行某些操作。这是promise的前身;它不那么高效或灵活。仅在必要时使用。

Useful for...
Single delayed operation Repeating operation Multiple sequential operations Multiple simultaneous operations
No Yes (recursive callbacks) Yes (nested callbacks) No

代码示例

通过XMLHttpRequest API加载资源的示例(run it live,并查看see the source):

function loadAsset(url, type, callback) {
  let xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.responseType = type;

  xhr.onload = function() {
    callback(xhr.response);
  };

  xhr.send();
}

function displayImage(blob) {
  let objectURL = URL.createObjectURL(blob);

  let image = document.createElement('img');
  image.src = objectURL;
  document.body.appendChild(image);
}

loadAsset('coffee.jpg', 'blob', displayImage);

缺陷

浏览器兼容性

非常好的一般支持,尽管API中回调的确切支持取决于特定的API。有关更具体的支持信息,请参阅您正在使用的API的参考文档。

更多信息

setTimeout()

setTimeout() 是一种允许您在经过任意时间后运行函数的方法

Useful for...
Single delayed operation Repeating operation Multiple sequential operations Multiple simultaneous operations
Yes Yes (recursive timeouts) Yes (nested timeouts) No

代码示例

这里浏览器将在执行匿名函数之前等待两秒钟,然后将显示警报消息(see it running livesee the source code):

let myGreeting = setTimeout(function() {
  alert('Hello, Mr. Universe!');
}, 2000)

缺陷

您可以使用递归的setTimeout()调用以类似于setInterval()的方式重复运行函数,使用如下代码:

let i = 1;
setTimeout(function run() {
  console.log(i);
  i++;

  setTimeout(run, 100);
}, 100);

递归setTimeout()setInterval()之间存在差异:

当你的代码有可能比你分配的时间间隔更长时间运行时,最好使用递归的setTimeout() ––这将使执行之间的时间间隔保持不变,无论代码执行多长时间,你不会得到错误。

浏览器兼容性

{{Compat("api.WindowOrWorkerGlobalScope.setTimeout")}}

更多信息

setInterval()

setInterval()函数允许重复执行一个函数,并设置时间间隔。不如requestAnimationFrame()有效率,但允许您选择运行速率/帧速率。

Useful for...
Single delayed operation Repeating operation Multiple sequential operations Multiple simultaneous operations
No Yes No (unless it is the same one) No

代码示例

以下函数创建一个新的Date()对象,使用toLocaleTimeString()从中提取时间字符串,然后在UI中显示它。然后我们使用setInterval()每秒运行一次,创建每秒更新一次的数字时钟的效果(see this livesee the source):

function displayTime() {
   let date = new Date();
   let time = date.toLocaleTimeString();
   document.getElementById('demo').textContent = time;
}

const createClock = setInterval(displayTime, 1000);

缺陷

浏览器兼容性

{{Compat("api.WindowOrWorkerGlobalScope.setInterval")}}

更多信息

requestAnimationFrame()

requestAnimationFrame()是一种允许您以给定当前浏览器/系统的最佳帧速率重复且高效地运行函数的方法。除非您需要特定的速率帧,否则您应该尽可能使用它而不要去使用setInterval()/recursive setTimeout()

Useful for...
Single delayed operation Repeating operation Multiple sequential operations Multiple simultaneous operations
No Yes No (unless it is the same one) No

代码示例

一个简单的动画旋转器;你可以查看example live on GitHub(参见 source code ):

const spinner = document.querySelector('div');
let rotateCount = 0;
let startTime = null;
let rAF;

function draw(timestamp) {
  if(!startTime) {
    startTime = timestamp;
  }

  let rotateCount = (timestamp - startTime) / 3;

  spinner.style.transform = 'rotate(' + rotateCount + 'deg)';

  if(rotateCount > 359) {
    rotateCount = 0;
  }

  rAF = requestAnimationFrame(draw);
}

draw();

缺陷

浏览器兼容性

{{Compat("api.Window.requestAnimationFrame")}}

更多信息

Promises

Promises 是一种JavaScript功能,允许您运行异步操作并等到它完全完成后再根据其结果运行另一个操作。 Promise是现代异步JavaScript的支柱。

Useful for...
Single delayed operation Repeating operation Multiple sequential operations Multiple simultaneous operations
No No Yes See Promise.all(), below

代码示例

以下代码从服务器获取图像并将其显示在 {{htmlelement("img")}} 元素中;(see it live alsothe source code):

fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
  let objectURL = URL.createObjectURL(myBlob);
  let image = document.createElement('img');
  image.src = objectURL;
  document.body.appendChild(image);
})
.catch(e => {
  console.log('There has been a problem with your fetch operation: ' + e.message);
});

缺陷

Promise链可能很复杂,难以解析。如果你嵌套了许多promises,你最终可能会遇到类似的麻烦来回调地狱。例如:

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.name == 'conflict') {
        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);
});

乃至:

remotedb.allDocs(...)
.then(resultOfAllDocs => {
  return localdb.put(...);
})
.then(resultOfPut => {
  return localdb.get(...);
})
.then(resultOfGet => {
  return localdb.put(...);
})
.catch(err => console.log(err));

这涵盖了很多基础知识。对于更完整的论述,请参阅诺兰劳森的We have a problem with promises

浏览器兼容性

{{Compat("javascript.builtins.Promise")}}

更多信息

Promise.all()

一种JavaScript功能,允许您等待多个promises完成,然后根据所有其他promises的结果运行进一步的操作。

Useful for...
Single delayed operation Repeating operation Multiple sequential operations Multiple simultaneous operations
No No No Yes

代码示例

以下示例从服务器获取多个资源,并使用Promise.all()等待所有资源可用,然后显示所有这些资源––  see it live,并查看source code

function fetchAndDecode(url, type) {
  // Returning the top level promise, so the result of the entire chain is returned out of the function
  return fetch(url).then(response => {
    // Depending on what type of file is being fetched, use the relevant function to decode its contents
    if(type === 'blob') {
      return response.blob();
    } else if(type === 'text') {
      return response.text();
    }
  })
  .catch(e => {
    console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
  });
}

// Call the fetchAndDecode() method to fetch the images and the text, and store their promises in variables
let coffee = fetchAndDecode('coffee.jpg', 'blob');
let tea = fetchAndDecode('tea.jpg', 'blob');
let description = fetchAndDecode('description.txt', 'text');

// Use Promise.all() to run code only when all three function calls have resolved
Promise.all([coffee, tea, description]).then(values => {
  console.log(values);
  // Store each value returned from the promises in separate variables; create object URLs from the blobs
  let objectURL1 = URL.createObjectURL(values[0]);
  let objectURL2 = URL.createObjectURL(values[1]);
  let descText = values[2];

  // Display the images in <img> elements
  let image1 = document.createElement('img');
  let image2 = document.createElement('img');
  image1.src = objectURL1;
  image2.src = objectURL2;
  document.body.appendChild(image1);
  document.body.appendChild(image2);

  // Display the text in a paragraph
  let para = document.createElement('p');
  para.textContent = descText;
  document.body.appendChild(para);
});

缺陷

浏览器兼容性

{{Compat("javascript.builtins.Promise.all")}}

更多信息

Async/await

构造在promises之上的语法糖,允许您使用更像编写同步回调代码的语法来运行异步操作。

Useful for...
Single delayed operation Repeating operation Multiple sequential operations Multiple simultaneous operations
No No Yes Yes (in combination with Promise.all())

代码示例

以下示例是我们之前看到的简单承诺示例的重构,该示例获取并显示图像,使用async / await编写(see it live,并查看source code):

async function myFetch() {
  let response = await fetch('coffee.jpg');
  let myBlob = await response.blob();

  let objectURL = URL.createObjectURL(myBlob);
  let image = document.createElement('img');
  image.src = objectURL;
  document.body.appendChild(image);
}

myFetch();

缺陷

浏览器兼容性

{{Compat("javascript.statements.async_function")}}

更多信息

{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}

本章内容