只有在ajax请求成功之后,才遍历foreach循环数组。

时间:2020-12-05 21:22:07

I'd like to iterate next time only after the ajax request is successful.

在ajax请求成功之后,我希望下次迭代。

You can clearly see what's going on here on the dev console. There is a console.log(...); function inside the foreach loop. And this condition should be always true. I can achieve this result using async: false in both of these AJAX request, however, it blocks UI.

您可以清楚地看到在dev控制台上发生了什么。有一个console.log(…);函数在foreach循环内。这个条件应该总是正确的。我可以使用async实现这个结果:在这两个AJAX请求中都是假的,但是它阻塞了UI。

In my particular situation, where it's not a normal webpage, just a single project, I could do this, however, I don't want to use it. I need other ideas.

在我的特殊情况下,它不是一个正常的网页,只是一个项目,我可以这么做,但是,我不想使用它。我需要其他的想法。

Console.log(....); function returns different values different times but It should always be the same value in the left, and in the right side.

Console.log(....);函数在不同的时间返回不同的值,但是在左边和右边都应该是相同的值。

For example: ESL_SC2 == habathcx - WRONG, ESL_SC2 == ESL_SC2 - CORRECT.

例如:ESL_SC2 == habathcx -错误,ESL_SC2 == ESL_SC2 -正确。


Open the console to see the result. Only look at the JS part.

打开控制台查看结果。只看JS部分。

$(function() {
  $("html").removeClass("no-js");
  $("#tabs").tabs();
  var xhr = new window.XMLHttpRequest(),
    users = [
      "ESL_SC2",
      "OgamingSC2",
      "cretetion",
      "freecodecamp",
      "storbeck",
      "habathcx",
      "RobotCaleb",
      "noobs2ninjas",
      "mhayia"
    ],
    index,
    ajaxDone,
    userResults = [],
    $search = $("#search"),
    keyCode,
    request;

  var getUserResults = function(callback) {
    index = 0;
    users.forEach(function(e) {
      $.ajax({
        url: "https://wind-bow.glitch.me/twitch-api/users/" + e,
        beforeSend: function() {
          ajaxDone = false;
        },
        success: function(d) {

          if (d.display_name !== undefined) {
            userResults[index] = {
              name: d.display_name
            };
            console.log(users[index] + " == " + e);
          }

          index++;

          if (index == users.length) {
            callback();
          }

          ajaxDone = true;
        },
        error: function() {
          alert(
            "AJAX Request failed. Please try again or contact using email n3olukas@gmail.com."
          );
        },
        datatype: "json",
        cache: false
      });
    });
  };

  getUserResults(function() {
    index = 0;
    users.forEach(function(e) {
      $.ajax({
        url: "https://wind-bow.glitch.me/twitch-api/streams/" + e,
        success: function(d) {
          if (d.stream !== null) {
            userResults[index].status = d.stream.channel !== undefined ? 'Online' : '';
            userResults[index].title = d.stream.channel !== undefined ? d.stream.channel.status != undefined ? d.stream.channel.status : '' : '';
            userResults[index].url = d.stream.channel !== undefined ? d.stream.channel.url !== undefined ? d.stream.channel.url : '' : '';
          }

          if (userResults[index].name) {
            $('#tabs-1 table').append('<tr><td>' + userResults[index].name + '</td><td>' + (userResults[index].status ?
              'Online' : 'Offline') + '</td><td>' + (userResults[index].title ?
              userResults[index].title : '') + '</td></tr>');
          }

          index++;

          $("body").addClass("loaded");
        },
        error: function() {
          alert(
            "AJAX Request failed. Please try again or contact using email n3olukas@gmail.com."
          );
        },
        datatype: "json",
        cache: false
      });
    });
  });
});
body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  background-image: url("https://images8.alphacoders.com/702/702959.jpg");
  background-attachment: fixed;
  background-size: cover;
}

* {
  box-sizing: border-box;
}

.text-center {
  text-align: center;
}

.input {
  overflow: hidden;
  white-space: nowrap;
}

#tabs ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

#tabs a {
  background-color: #f44336;
  width: 33.33%;
  width: calc(100% / 3);
  padding: 10px 0;
  float: left;
}

#search {
  width: 100%;
  height: 3.125rem;
  border: none;
  font-size: 13px;
  color: #4f5b66;
  padding: 0 .9375rem;
}

.three-dots {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

table {
  width: 100%;
  border-spacing: 0;
  border-collapse: collapse;
  table-layout: fixed;
}

table tr {
  background-color: #f44336;
  color: #fff;
}

tr:first-child {
  background-color: #673ab7;
}

table td {
  padding: 0.625rem 0;
}

:focus {
  outline: 0;
}

.ui-state-active a {
  background-color: #2196f3 !important;
}

footer {
  font-size: 0.85rem;
  margin: 1rem 0;
}

a {
  text-decoration: none;
  color: #fff;
  position: relative;
}

footer a:before {
  content: "";
  position: absolute;
  width: 100%;
  height: 0.0625rem;
  bottom: 0;
  left: 0;
  background-color: #fff;
  visibility: hidden;
  -webkit-transform: scaleX(0);
  transform: scaleX(0);
  -webkit-transition: all 0.3s ease-in-out 0s;
  transition: all 0.3s ease-in-out 0s;
}

footer a:hover:before {
  visibility: visible;
  -webkit-transform: scaleX(1);
  transform: scaleX(1);
}

.search-results {
  background: #fff;
  margin: 1.5625rem 0;
  border-left: 0.3125rem solid #0ebeff;
  opacity: 0;
  display: none;
}

.search-results h4,
.search-results p {
  margin: 0;
  padding: 0.625rem;
  text-align: left;
}

.search-results a {
  color: #0ebeff;
  display: inline-block;
  margin: 1rem 0;
}

.search-results a:before {
  background-color: #0ebeff;
}

.wikisearch-container {
  width: 65%;
  margin: 2.5rem auto 0;
}


/* Screen loader */

#loader-wrapper {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
}

#loader {
  display: block;
  position: relative;
  left: 50%;
  top: 50%;
  width: 9.375rem;
  height: 9.375rem;
  margin: -4.6875rem 0 0 -4.6875rem;
  border-radius: 50%;
  border: 0.1875rem solid transparent;
  border-top-color: #fff;
  -webkit-animation: spin 1.75s linear infinite;
  /* Chrome, Opera 15+ Safari 5+ */
  animation: spin 1.75s linear infinite;
  /* Chrome, Firefox  16+, IE 10+, Opera */
  z-index: 1001;
}

#loader:before {
  content: "";
  position: absolute;
  top: 0.3125rem;
  left: 0.3125rem;
  right: 0.3125rem;
  bottom: 0.3125rem;
  border-radius: 50%;
  border: 0.1875rem solid transparent;
  border-top-color: #f7d130;
  -webkit-animation: spin 1.5s linear infinite;
  /* Chrome, Opera 15+ Safari 5+ */
  animation: spin 1.5s linear infinite;
  /* Chrome, Firefox  16+, IE 10+, Opera */
}

#loader:after {
  content: "";
  position: absolute;
  top: 0.9375rem;
  left: 0.9375rem;
  right: 0.9375rem;
  bottom: 0.9375rem;
  border-radius: 50%;
  border: 0.1875rem solid transparent;
  border-top-color: #0fff;
  -webkit-animation: spin 1.25s linear infinite;
  /* Chrome, Opera 15+ Safari 5+ */
  animation: spin 1.25s linear infinite;
  /* Chrome, Firefox  16+, IE 10+, Opera */
}

@-webkit-keyframes spin {
  0% {
    -webkit-transform: rotate(0deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(0deg);
    /* IE 9 */
    transform: rotate(0deg);
    /* Firefox 16+, IE 10+, Opera */
  }
  100% {
    -webkit-transform: rotate(360deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(360deg);
    /* IE 9 */
    transform: rotate(360deg);
    /* Firefox 16+, IE 10+, Opera */
  }
}

@keyframes spin {
  0% {
    -webkit-transform: rotate(0deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(0deg);
    /* IE 9 */
    transform: rotate(0deg);
    /* Firefox 16+, IE 10+, Opera */
  }
  100% {
    -webkit-transform: rotate(360deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(360deg);
    /* IE 9 */
    transform: rotate(360deg);
    /* Firefox 16+, IE 10+, Opera */
  }
}

.loaded #loader-wrapper,
.loader-section {
  position: fixed;
  top: 0;
  width: 50%;
  height: 100%;
  background: #000428;
  z-index: 1000;
}

#loader-wrapper .loader-section.section-left {
  left: 0;
}

#loader-wrapper .loader-section.section-right {
  right: 0;
}

.loaded #loader-wrapper .loader-section.section-left {
  -webkit-transform: translateX(-100%);
  /* Chrome, Opera 15+, Sadari 3.1+ */
  -ms-transform: transalteX(-100%);
  /* IE 9 */
  transform: translateX(-100%);
  /* Firefox 16+ IE 10+, Opera */
  -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.loaded #loader-wrapper .loader-section.section-right {
  -webkit-transform: translateX(100%);
  /* Chrome, Opera 15+, Sadari 3.1+ */
  -ms-transform: transalteX(100%);
  /* IE 9 */
  transform: translateX(100%);
  /* Firefox 16+ IE 10+, Opera */
  -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.loaded #loader {
  opacity: 0;
  position: fixed;
  -webkit-transition: all 0.3s ease-out;
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.3s ease-out;
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.loaded #loader-wrapper {
  visibility: hidden;
  -webkit-transition: all 0.3s ease-out;
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.3s ease-out;
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.no-js #loader-wrapper {
  display: none;
}


/* Loading animation */


/* Loading animation */

@keyframes lds-eclipse {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  50% {
    -webkit-transform: rotate(180deg);
    transform: rotate(180deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

@-webkit-keyframes lds-eclipse {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  50% {
    -webkit-transform: rotate(180deg);
    transform: rotate(180deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

.loading {
  position: relative;
  top: 0.59375rem;
  right: 0.9375rem;
  pointer-events: none;
  display: none;
}

.lds-eclipse {
  -webkit-animation: lds-eclipse 1s linear infinite;
  animation: lds-eclipse 1s linear infinite;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  margin-left: auto;
  box-shadow: 0.08rem 0 0 #0ebeff;
}

@media (max-width: 71.875em) {
  .wikisearch-container {
    width: 75%;
  }
}

@media (max-width: 50em) {
  .wikisearch-container {
    width: 85%;
  }
}

@media (max-width: 17.96875em) {
  .wikisearch-container {
    width: 100%;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div id="loader-wrapper">
  <div id="loader">
  </div>

  <div class="loader-section section-left"></div>
  <div class="loader-section section-right"></div>
</div>
<div class="twitchtv-container text-center">
  <div id="tabs">
    <ul>
      <li><a href="#tabs-1">All</a></li>
      <li><a href="#tabs-2">Online</a></li>
      <li><a href="#tabs-3">Offline</a></li>
    </ul>
    <input type="text" id="search" placeholder="Search...">
    <div id="tabs-1">
      <table>
        <tr>
          <td>User</td>
          <td>Status</td>
          <td>Title</td>
        </tr>
      </table>
    </div>
    <div id="tabs-2">
      <table>
        <tr>
          <td>User</td>
          <td>Status</td>
          <td>Title</td>
        </tr>
      </table>
    </div>
    <div id="tabs-3">
      <table>
        <tr>
          <td>User</td>
          <td>Status</td>
        </tr>
      </table>
    </div>
  </div>
  <footer>
    <a href="https://codepen.io/Kestis500">Created by LukasLSC</a>
  </footer>
</div>

2 个解决方案

#1


1  

While there are techniques for "asynchronous iteration", fetching one user after another is a slow way to make multiple, separate requests. For a situation like this, it is much faster to make the fetches in parallel. This is what you're currently doing, but as you've realized the challenge is getting the responses back in the same order as the requests. This is a problem promises solve well (using Promise.all and fetch). Since you're already using jQuery, you can also accomplish it with jQuery's Deferred objects and $.when():

虽然有“异步迭代”的技术,但一个接一个地吸引一个用户是一个缓慢的方法,可以实现多个独立的请求。对于这样的情况,让fetchin并行的速度要快得多。这是您当前正在做的事情,但您已经意识到,挑战是将响应以与请求相同的顺序返回。这是一个解决问题的承诺。所有和获取)。既然您已经使用了jQuery,那么您还可以使用jQuery的延迟对象和$ when()来完成它:

var users = [
  "ESL_SC2",
  "OgamingSC2",
  "cretetion",
  "freecodecamp",
  "storbeck",
  "habathcx",
  "RobotCaleb",
  "noobs2ninjas",
  "mhayia"
];

// Transform array of usernames into an array of jQuery deferreds for
// each request. Note that this is actually sending all the requests
// in parallel.
var requests = users.map(function(user) {
  // No need to handle success/failures here, we'll chain those handlers
  // onto the `$.when()` call below.
  return $.ajax({
    url: "https://wind-bow.glitch.me/twitch-api/users/" + user,
    datatype: "json",
    cache: false
  });
});

// Use jQuery.when() to wait for all requests to complete, and provide
// the responses in order (this is the purpose of $.when() or Promise.all()).
// Note that $.when() expects multiple arguments so we need accomodate that
// by spreading our array with Function.prototype.apply(). (This could be
// written more simply in ES2015 as $.when(...requests)).
$.when.apply(null, requests)
  // done() is called after all requests are complete. All responses are
  // passed as arguments in the order the requests were sent.
  .done(function() {
    // Loop through all arguments. Using a `for` loop because built-in
    // `arguments` object isn't a real array, so we can't use
    // arguments.forEach().
    for (var i=0; i<arguments.length; i++) {
      var response = arguments[i];
      // Response is an array, the first element is the user object we want.
      var user = response[0];
      console.log(users[i] + " == " + user.display_name);
    }
  })
  // If any request fails, it will be handled here.
  .fail(function() {
    alert('User request failed.');
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

For the sake of example, here are two network waterfalls recorded with Chrome's dev tools, simulating a "fast 3G" speed. The parallel version is a good 5x faster.

举个例子,这里有两个用Chrome的开发工具记录的网络瀑布,模拟了“快速3G”的速度。并行版本的速度要快5倍。

Fetching the users in parallel finishes in 1 second: 只有在ajax请求成功之后,才遍历foreach循环数组。

在1秒内以并行方式获取用户:

Fetching the users sequentially takes a good 5 seconds: 只有在ajax请求成功之后,才遍历foreach循环数组。

按顺序抓取用户需要5秒:

#2


1  

Remove the forEach function and loop recursively calling the function getUserResults().

删除forEach函数,并递归调用函数getUserResults()。

Look at this code snippet

$(function() {
  $("html").removeClass("no-js");
  $("#tabs").tabs();
  var xhr = new window.XMLHttpRequest(),
    users = [
      "ESL_SC2",
      "OgamingSC2",
      "cretetion",
      "freecodecamp",
      "storbeck",
      "habathcx",
      "RobotCaleb",
      "noobs2ninjas",
      "mhayia"
    ],
    index,
    ajaxDone,
    userResults = [],
    $search = $("#search"),
    keyCode,
    request;

  var getUserResults = function(callback, index) {
    //index = 0;
    if (index === users.length) {
      callback();
      return;
    }

    //users.forEach(function(e) {
    $.ajax({
      url: "https://wind-bow.glitch.me/twitch-api/users/" + users[index],
      beforeSend: function() {
        ajaxDone = false;
      },
      success: function(d) {
        if (d.display_name !== undefined) {
          userResults[index] = {
            name: d.display_name
          };
          console.log(users[index] + " == " + users[index]);
        }

        getUserResults(callback, ++index);

        //if (index == users.length) {
        //callback();
        //}

        ajaxDone = true;
      },
      error: function() {
        alert(
          "AJAX Request failed. Please try again or contact using email n3olukas@gmail.com."
        );
      },
      datatype: "json",
      cache: false
    });
    //});
  };

  getUserResults(function() {
    index = 0;
    users.forEach(function(e) {
      $.ajax({
        url: "https://wind-bow.glitch.me/twitch-api/streams/" + e,
        success: function(d) {
          if (d.stream !== null) {
            userResults[index].status = d.stream.channel !== undefined ? 'Online' : '';
            userResults[index].title = d.stream.channel !== undefined ? d.stream.channel.status != undefined ? d.stream.channel.status : '' : '';
            userResults[index].url = d.stream.channel !== undefined ? d.stream.channel.url !== undefined ? d.stream.channel.url : '' : '';
          }

          if (userResults[index].name) {
            $('#tabs-1 table').append('<tr><td>' + userResults[index].name + '</td><td>' + (userResults[index].status ?
              'Online' : 'Offline') + '</td><td>' + (userResults[index].title ?
              userResults[index].title : '') + '</td></tr>');
          }

          index++;

          $("body").addClass("loaded");
        },
        error: function() {
          alert(
            "AJAX Request failed. Please try again or contact using email n3olukas@gmail.com."
          );
        },
        datatype: "json",
        cache: false
      });
    });
  }, 0);
});
body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  background-image: url("https://images8.alphacoders.com/702/702959.jpg");
  background-attachment: fixed;
  background-size: cover;
}

* {
  box-sizing: border-box;
}

.text-center {
  text-align: center;
}

.input {
  overflow: hidden;
  white-space: nowrap;
}

#tabs ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

#tabs a {
  background-color: #f44336;
  width: 33.33%;
  width: calc(100% / 3);
  padding: 10px 0;
  float: left;
}

#search {
  width: 100%;
  height: 3.125rem;
  border: none;
  font-size: 13px;
  color: #4f5b66;
  padding: 0 .9375rem;
}

.three-dots {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

table {
  width: 100%;
  border-spacing: 0;
  border-collapse: collapse;
  table-layout: fixed;
}

table tr {
  background-color: #f44336;
  color: #fff;
}

tr:first-child {
  background-color: #673ab7;
}

table td {
  padding: 0.625rem 0;
}

:focus {
  outline: 0;
}

.ui-state-active a {
  background-color: #2196f3 !important;
}

footer {
  font-size: 0.85rem;
  margin: 1rem 0;
}

a {
  text-decoration: none;
  color: #fff;
  position: relative;
}

footer a:before {
  content: "";
  position: absolute;
  width: 100%;
  height: 0.0625rem;
  bottom: 0;
  left: 0;
  background-color: #fff;
  visibility: hidden;
  -webkit-transform: scaleX(0);
  transform: scaleX(0);
  -webkit-transition: all 0.3s ease-in-out 0s;
  transition: all 0.3s ease-in-out 0s;
}

footer a:hover:before {
  visibility: visible;
  -webkit-transform: scaleX(1);
  transform: scaleX(1);
}

.search-results {
  background: #fff;
  margin: 1.5625rem 0;
  border-left: 0.3125rem solid #0ebeff;
  opacity: 0;
  display: none;
}

.search-results h4,
.search-results p {
  margin: 0;
  padding: 0.625rem;
  text-align: left;
}

.search-results a {
  color: #0ebeff;
  display: inline-block;
  margin: 1rem 0;
}

.search-results a:before {
  background-color: #0ebeff;
}

.wikisearch-container {
  width: 65%;
  margin: 2.5rem auto 0;
}


/* Screen loader */

#loader-wrapper {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
}

#loader {
  display: block;
  position: relative;
  left: 50%;
  top: 50%;
  width: 9.375rem;
  height: 9.375rem;
  margin: -4.6875rem 0 0 -4.6875rem;
  border-radius: 50%;
  border: 0.1875rem solid transparent;
  border-top-color: #fff;
  -webkit-animation: spin 1.75s linear infinite;
  /* Chrome, Opera 15+ Safari 5+ */
  animation: spin 1.75s linear infinite;
  /* Chrome, Firefox  16+, IE 10+, Opera */
  z-index: 1001;
}

#loader:before {
  content: "";
  position: absolute;
  top: 0.3125rem;
  left: 0.3125rem;
  right: 0.3125rem;
  bottom: 0.3125rem;
  border-radius: 50%;
  border: 0.1875rem solid transparent;
  border-top-color: #f7d130;
  -webkit-animation: spin 1.5s linear infinite;
  /* Chrome, Opera 15+ Safari 5+ */
  animation: spin 1.5s linear infinite;
  /* Chrome, Firefox  16+, IE 10+, Opera */
}

#loader:after {
  content: "";
  position: absolute;
  top: 0.9375rem;
  left: 0.9375rem;
  right: 0.9375rem;
  bottom: 0.9375rem;
  border-radius: 50%;
  border: 0.1875rem solid transparent;
  border-top-color: #0fff;
  -webkit-animation: spin 1.25s linear infinite;
  /* Chrome, Opera 15+ Safari 5+ */
  animation: spin 1.25s linear infinite;
  /* Chrome, Firefox  16+, IE 10+, Opera */
}

@-webkit-keyframes spin {
  0% {
    -webkit-transform: rotate(0deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(0deg);
    /* IE 9 */
    transform: rotate(0deg);
    /* Firefox 16+, IE 10+, Opera */
  }
  100% {
    -webkit-transform: rotate(360deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(360deg);
    /* IE 9 */
    transform: rotate(360deg);
    /* Firefox 16+, IE 10+, Opera */
  }
}

@keyframes spin {
  0% {
    -webkit-transform: rotate(0deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(0deg);
    /* IE 9 */
    transform: rotate(0deg);
    /* Firefox 16+, IE 10+, Opera */
  }
  100% {
    -webkit-transform: rotate(360deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(360deg);
    /* IE 9 */
    transform: rotate(360deg);
    /* Firefox 16+, IE 10+, Opera */
  }
}

.loaded #loader-wrapper,
.loader-section {
  position: fixed;
  top: 0;
  width: 50%;
  height: 100%;
  background: #000428;
  z-index: 1000;
}

#loader-wrapper .loader-section.section-left {
  left: 0;
}

#loader-wrapper .loader-section.section-right {
  right: 0;
}

.loaded #loader-wrapper .loader-section.section-left {
  -webkit-transform: translateX(-100%);
  /* Chrome, Opera 15+, Sadari 3.1+ */
  -ms-transform: transalteX(-100%);
  /* IE 9 */
  transform: translateX(-100%);
  /* Firefox 16+ IE 10+, Opera */
  -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.loaded #loader-wrapper .loader-section.section-right {
  -webkit-transform: translateX(100%);
  /* Chrome, Opera 15+, Sadari 3.1+ */
  -ms-transform: transalteX(100%);
  /* IE 9 */
  transform: translateX(100%);
  /* Firefox 16+ IE 10+, Opera */
  -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.loaded #loader {
  opacity: 0;
  position: fixed;
  -webkit-transition: all 0.3s ease-out;
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.3s ease-out;
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.loaded #loader-wrapper {
  visibility: hidden;
  -webkit-transition: all 0.3s ease-out;
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.3s ease-out;
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.no-js #loader-wrapper {
  display: none;
}


/* Loading animation */


/* Loading animation */

@keyframes lds-eclipse {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  50% {
    -webkit-transform: rotate(180deg);
    transform: rotate(180deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

@-webkit-keyframes lds-eclipse {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  50% {
    -webkit-transform: rotate(180deg);
    transform: rotate(180deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

.loading {
  position: relative;
  top: 0.59375rem;
  right: 0.9375rem;
  pointer-events: none;
  display: none;
}

.lds-eclipse {
  -webkit-animation: lds-eclipse 1s linear infinite;
  animation: lds-eclipse 1s linear infinite;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  margin-left: auto;
  box-shadow: 0.08rem 0 0 #0ebeff;
}

@media (max-width: 71.875em) {
  .wikisearch-container {
    width: 75%;
  }
}

@media (max-width: 50em) {
  .wikisearch-container {
    width: 85%;
  }
}

@media (max-width: 17.96875em) {
  .wikisearch-container {
    width: 100%;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div id="loader-wrapper">
  <div id="loader">
  </div>

  <div class="loader-section section-left"></div>
  <div class="loader-section section-right"></div>
</div>
<div class="twitchtv-container text-center">
  <div id="tabs">
    <ul>
      <li><a href="#tabs-1">All</a></li>
      <li><a href="#tabs-2">Online</a></li>
      <li><a href="#tabs-3">Offline</a></li>
    </ul>
    <input type="text" id="search" placeholder="Search...">
    <div id="tabs-1">
      <table>
        <tr>
          <td>User</td>
          <td>Status</td>
          <td>Title</td>
        </tr>
      </table>
    </div>
    <div id="tabs-2">
      <table>
        <tr>
          <td>User</td>
          <td>Status</td>
          <td>Title</td>
        </tr>
      </table>
    </div>
    <div id="tabs-3">
      <table>
        <tr>
          <td>User</td>
          <td>Status</td>
        </tr>
      </table>
    </div>
  </div>
  <footer>
    <a href="https://codepen.io/Kestis500">Created by LukasLSC</a>
  </footer>
</div>

See? now the prints are synchronized.

看到了吗?现在指纹是同步的。

#1


1  

While there are techniques for "asynchronous iteration", fetching one user after another is a slow way to make multiple, separate requests. For a situation like this, it is much faster to make the fetches in parallel. This is what you're currently doing, but as you've realized the challenge is getting the responses back in the same order as the requests. This is a problem promises solve well (using Promise.all and fetch). Since you're already using jQuery, you can also accomplish it with jQuery's Deferred objects and $.when():

虽然有“异步迭代”的技术,但一个接一个地吸引一个用户是一个缓慢的方法,可以实现多个独立的请求。对于这样的情况,让fetchin并行的速度要快得多。这是您当前正在做的事情,但您已经意识到,挑战是将响应以与请求相同的顺序返回。这是一个解决问题的承诺。所有和获取)。既然您已经使用了jQuery,那么您还可以使用jQuery的延迟对象和$ when()来完成它:

var users = [
  "ESL_SC2",
  "OgamingSC2",
  "cretetion",
  "freecodecamp",
  "storbeck",
  "habathcx",
  "RobotCaleb",
  "noobs2ninjas",
  "mhayia"
];

// Transform array of usernames into an array of jQuery deferreds for
// each request. Note that this is actually sending all the requests
// in parallel.
var requests = users.map(function(user) {
  // No need to handle success/failures here, we'll chain those handlers
  // onto the `$.when()` call below.
  return $.ajax({
    url: "https://wind-bow.glitch.me/twitch-api/users/" + user,
    datatype: "json",
    cache: false
  });
});

// Use jQuery.when() to wait for all requests to complete, and provide
// the responses in order (this is the purpose of $.when() or Promise.all()).
// Note that $.when() expects multiple arguments so we need accomodate that
// by spreading our array with Function.prototype.apply(). (This could be
// written more simply in ES2015 as $.when(...requests)).
$.when.apply(null, requests)
  // done() is called after all requests are complete. All responses are
  // passed as arguments in the order the requests were sent.
  .done(function() {
    // Loop through all arguments. Using a `for` loop because built-in
    // `arguments` object isn't a real array, so we can't use
    // arguments.forEach().
    for (var i=0; i<arguments.length; i++) {
      var response = arguments[i];
      // Response is an array, the first element is the user object we want.
      var user = response[0];
      console.log(users[i] + " == " + user.display_name);
    }
  })
  // If any request fails, it will be handled here.
  .fail(function() {
    alert('User request failed.');
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

For the sake of example, here are two network waterfalls recorded with Chrome's dev tools, simulating a "fast 3G" speed. The parallel version is a good 5x faster.

举个例子,这里有两个用Chrome的开发工具记录的网络瀑布,模拟了“快速3G”的速度。并行版本的速度要快5倍。

Fetching the users in parallel finishes in 1 second: 只有在ajax请求成功之后,才遍历foreach循环数组。

在1秒内以并行方式获取用户:

Fetching the users sequentially takes a good 5 seconds: 只有在ajax请求成功之后,才遍历foreach循环数组。

按顺序抓取用户需要5秒:

#2


1  

Remove the forEach function and loop recursively calling the function getUserResults().

删除forEach函数,并递归调用函数getUserResults()。

Look at this code snippet

$(function() {
  $("html").removeClass("no-js");
  $("#tabs").tabs();
  var xhr = new window.XMLHttpRequest(),
    users = [
      "ESL_SC2",
      "OgamingSC2",
      "cretetion",
      "freecodecamp",
      "storbeck",
      "habathcx",
      "RobotCaleb",
      "noobs2ninjas",
      "mhayia"
    ],
    index,
    ajaxDone,
    userResults = [],
    $search = $("#search"),
    keyCode,
    request;

  var getUserResults = function(callback, index) {
    //index = 0;
    if (index === users.length) {
      callback();
      return;
    }

    //users.forEach(function(e) {
    $.ajax({
      url: "https://wind-bow.glitch.me/twitch-api/users/" + users[index],
      beforeSend: function() {
        ajaxDone = false;
      },
      success: function(d) {
        if (d.display_name !== undefined) {
          userResults[index] = {
            name: d.display_name
          };
          console.log(users[index] + " == " + users[index]);
        }

        getUserResults(callback, ++index);

        //if (index == users.length) {
        //callback();
        //}

        ajaxDone = true;
      },
      error: function() {
        alert(
          "AJAX Request failed. Please try again or contact using email n3olukas@gmail.com."
        );
      },
      datatype: "json",
      cache: false
    });
    //});
  };

  getUserResults(function() {
    index = 0;
    users.forEach(function(e) {
      $.ajax({
        url: "https://wind-bow.glitch.me/twitch-api/streams/" + e,
        success: function(d) {
          if (d.stream !== null) {
            userResults[index].status = d.stream.channel !== undefined ? 'Online' : '';
            userResults[index].title = d.stream.channel !== undefined ? d.stream.channel.status != undefined ? d.stream.channel.status : '' : '';
            userResults[index].url = d.stream.channel !== undefined ? d.stream.channel.url !== undefined ? d.stream.channel.url : '' : '';
          }

          if (userResults[index].name) {
            $('#tabs-1 table').append('<tr><td>' + userResults[index].name + '</td><td>' + (userResults[index].status ?
              'Online' : 'Offline') + '</td><td>' + (userResults[index].title ?
              userResults[index].title : '') + '</td></tr>');
          }

          index++;

          $("body").addClass("loaded");
        },
        error: function() {
          alert(
            "AJAX Request failed. Please try again or contact using email n3olukas@gmail.com."
          );
        },
        datatype: "json",
        cache: false
      });
    });
  }, 0);
});
body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  background-image: url("https://images8.alphacoders.com/702/702959.jpg");
  background-attachment: fixed;
  background-size: cover;
}

* {
  box-sizing: border-box;
}

.text-center {
  text-align: center;
}

.input {
  overflow: hidden;
  white-space: nowrap;
}

#tabs ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

#tabs a {
  background-color: #f44336;
  width: 33.33%;
  width: calc(100% / 3);
  padding: 10px 0;
  float: left;
}

#search {
  width: 100%;
  height: 3.125rem;
  border: none;
  font-size: 13px;
  color: #4f5b66;
  padding: 0 .9375rem;
}

.three-dots {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

table {
  width: 100%;
  border-spacing: 0;
  border-collapse: collapse;
  table-layout: fixed;
}

table tr {
  background-color: #f44336;
  color: #fff;
}

tr:first-child {
  background-color: #673ab7;
}

table td {
  padding: 0.625rem 0;
}

:focus {
  outline: 0;
}

.ui-state-active a {
  background-color: #2196f3 !important;
}

footer {
  font-size: 0.85rem;
  margin: 1rem 0;
}

a {
  text-decoration: none;
  color: #fff;
  position: relative;
}

footer a:before {
  content: "";
  position: absolute;
  width: 100%;
  height: 0.0625rem;
  bottom: 0;
  left: 0;
  background-color: #fff;
  visibility: hidden;
  -webkit-transform: scaleX(0);
  transform: scaleX(0);
  -webkit-transition: all 0.3s ease-in-out 0s;
  transition: all 0.3s ease-in-out 0s;
}

footer a:hover:before {
  visibility: visible;
  -webkit-transform: scaleX(1);
  transform: scaleX(1);
}

.search-results {
  background: #fff;
  margin: 1.5625rem 0;
  border-left: 0.3125rem solid #0ebeff;
  opacity: 0;
  display: none;
}

.search-results h4,
.search-results p {
  margin: 0;
  padding: 0.625rem;
  text-align: left;
}

.search-results a {
  color: #0ebeff;
  display: inline-block;
  margin: 1rem 0;
}

.search-results a:before {
  background-color: #0ebeff;
}

.wikisearch-container {
  width: 65%;
  margin: 2.5rem auto 0;
}


/* Screen loader */

#loader-wrapper {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
}

#loader {
  display: block;
  position: relative;
  left: 50%;
  top: 50%;
  width: 9.375rem;
  height: 9.375rem;
  margin: -4.6875rem 0 0 -4.6875rem;
  border-radius: 50%;
  border: 0.1875rem solid transparent;
  border-top-color: #fff;
  -webkit-animation: spin 1.75s linear infinite;
  /* Chrome, Opera 15+ Safari 5+ */
  animation: spin 1.75s linear infinite;
  /* Chrome, Firefox  16+, IE 10+, Opera */
  z-index: 1001;
}

#loader:before {
  content: "";
  position: absolute;
  top: 0.3125rem;
  left: 0.3125rem;
  right: 0.3125rem;
  bottom: 0.3125rem;
  border-radius: 50%;
  border: 0.1875rem solid transparent;
  border-top-color: #f7d130;
  -webkit-animation: spin 1.5s linear infinite;
  /* Chrome, Opera 15+ Safari 5+ */
  animation: spin 1.5s linear infinite;
  /* Chrome, Firefox  16+, IE 10+, Opera */
}

#loader:after {
  content: "";
  position: absolute;
  top: 0.9375rem;
  left: 0.9375rem;
  right: 0.9375rem;
  bottom: 0.9375rem;
  border-radius: 50%;
  border: 0.1875rem solid transparent;
  border-top-color: #0fff;
  -webkit-animation: spin 1.25s linear infinite;
  /* Chrome, Opera 15+ Safari 5+ */
  animation: spin 1.25s linear infinite;
  /* Chrome, Firefox  16+, IE 10+, Opera */
}

@-webkit-keyframes spin {
  0% {
    -webkit-transform: rotate(0deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(0deg);
    /* IE 9 */
    transform: rotate(0deg);
    /* Firefox 16+, IE 10+, Opera */
  }
  100% {
    -webkit-transform: rotate(360deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(360deg);
    /* IE 9 */
    transform: rotate(360deg);
    /* Firefox 16+, IE 10+, Opera */
  }
}

@keyframes spin {
  0% {
    -webkit-transform: rotate(0deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(0deg);
    /* IE 9 */
    transform: rotate(0deg);
    /* Firefox 16+, IE 10+, Opera */
  }
  100% {
    -webkit-transform: rotate(360deg);
    /* Chrome, Opera 15+, Safari 3.1+ */
    -ms-transform: rotate(360deg);
    /* IE 9 */
    transform: rotate(360deg);
    /* Firefox 16+, IE 10+, Opera */
  }
}

.loaded #loader-wrapper,
.loader-section {
  position: fixed;
  top: 0;
  width: 50%;
  height: 100%;
  background: #000428;
  z-index: 1000;
}

#loader-wrapper .loader-section.section-left {
  left: 0;
}

#loader-wrapper .loader-section.section-right {
  right: 0;
}

.loaded #loader-wrapper .loader-section.section-left {
  -webkit-transform: translateX(-100%);
  /* Chrome, Opera 15+, Sadari 3.1+ */
  -ms-transform: transalteX(-100%);
  /* IE 9 */
  transform: translateX(-100%);
  /* Firefox 16+ IE 10+, Opera */
  -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.loaded #loader-wrapper .loader-section.section-right {
  -webkit-transform: translateX(100%);
  /* Chrome, Opera 15+, Sadari 3.1+ */
  -ms-transform: transalteX(100%);
  /* IE 9 */
  transform: translateX(100%);
  /* Firefox 16+ IE 10+, Opera */
  -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.loaded #loader {
  opacity: 0;
  position: fixed;
  -webkit-transition: all 0.3s ease-out;
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.3s ease-out;
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.loaded #loader-wrapper {
  visibility: hidden;
  -webkit-transition: all 0.3s ease-out;
  /* Android 2.1+ Chrome 1-25, iOS 3.2-6.1, Safari 3.2-6 */
  transition: all 0.3s ease-out;
  /* Chrome 26, Firefox 16+, iOS 7+, IE 10+, Opera, Safari 6.1+ */
}

.no-js #loader-wrapper {
  display: none;
}


/* Loading animation */


/* Loading animation */

@keyframes lds-eclipse {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  50% {
    -webkit-transform: rotate(180deg);
    transform: rotate(180deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

@-webkit-keyframes lds-eclipse {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  50% {
    -webkit-transform: rotate(180deg);
    transform: rotate(180deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

.loading {
  position: relative;
  top: 0.59375rem;
  right: 0.9375rem;
  pointer-events: none;
  display: none;
}

.lds-eclipse {
  -webkit-animation: lds-eclipse 1s linear infinite;
  animation: lds-eclipse 1s linear infinite;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  margin-left: auto;
  box-shadow: 0.08rem 0 0 #0ebeff;
}

@media (max-width: 71.875em) {
  .wikisearch-container {
    width: 75%;
  }
}

@media (max-width: 50em) {
  .wikisearch-container {
    width: 85%;
  }
}

@media (max-width: 17.96875em) {
  .wikisearch-container {
    width: 100%;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div id="loader-wrapper">
  <div id="loader">
  </div>

  <div class="loader-section section-left"></div>
  <div class="loader-section section-right"></div>
</div>
<div class="twitchtv-container text-center">
  <div id="tabs">
    <ul>
      <li><a href="#tabs-1">All</a></li>
      <li><a href="#tabs-2">Online</a></li>
      <li><a href="#tabs-3">Offline</a></li>
    </ul>
    <input type="text" id="search" placeholder="Search...">
    <div id="tabs-1">
      <table>
        <tr>
          <td>User</td>
          <td>Status</td>
          <td>Title</td>
        </tr>
      </table>
    </div>
    <div id="tabs-2">
      <table>
        <tr>
          <td>User</td>
          <td>Status</td>
          <td>Title</td>
        </tr>
      </table>
    </div>
    <div id="tabs-3">
      <table>
        <tr>
          <td>User</td>
          <td>Status</td>
        </tr>
      </table>
    </div>
  </div>
  <footer>
    <a href="https://codepen.io/Kestis500">Created by LukasLSC</a>
  </footer>
</div>

See? now the prints are synchronized.

看到了吗?现在指纹是同步的。