首页 > 解决方案 > 如何使这个递归函数异步?

问题描述

function SetText(gg = `textttttt `, cmd = `sudo --info`) {
    window.scrollTo({ top: 0, behavior: 'smooth' });
    if (document.getElementsByClassName('demo').length > 0) {
        var i = 0;
        var speed = 60;

        document.getElementsByClassName('demo')[0].innerHTML = `<code class="shell-session demo hljs nginx"><span class="hljs-attribute">Website</span> <span class="hljs-regexp">~ $</span> ${cmd}`;

        function typeItOut() {
            if (i < gg.length) {
                document.getElementsByClassName('demo')[0].innerHTML += gg.charAt(i);
                i++;
                setTimeout(typeItOut, speed);
            }
        }
        setTimeout(typeItOut, 1800);
    }
}

所以这就是代码,我希望每次我点击我网站上的一个按钮时,它都会等到递归完成然后再开始另一个......

标签: javascripthtmlnode.jswebsitespark

解决方案


你能用async/await吗?

如果可以的话,这将使通过给定的超时持续时间通过字符串“暂停”每次迭代变得更容易(参见handleIterateString下面的类函数)。

此异步handleIterateString函数将在每个关键字处“暂停” await,并等待await表达式返回的承诺被解决。只有这样,它才会继续执行该async功能。

此外,您可以“暂停”async函数的执行,您可以在其中通过字符串启动新的完整迭代(请参阅下面的await demo.handleIterateString调用async function SetText

通过这种方式,您可以等待整个迭代(即键入行为)完成,然后再减少您的点击队列计数。

如果您的队列中还有点击事件,您可以在此时SetText递归调用。


简而言之: usingasync/await可以更轻松地控制打字行为的速度,并在执行其他任何操作之前等待您的打字行为完成。

尝试运行下面的代码片段。

class Typer {
  /**
   * @description delays execution for a given amount of time
   * @param {number} ms - time in milliseconds
   * @returns {Promise<void>}
   *
   * @private
   */
  #delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

  /**
   * @description html for for displaying queue information
   * @returns {{hasQueue: string, noQueue: string}}
   * @private
   */
  get #html() {
    return {
      hasQueue: `Click events waiting in queue: <span class="tag is-danger is-light is-large">${this.state.queueCount}</span>`,
      noQueue: 'Queue is clear',
    };
  }

  /**
   * @description renders queue count information
   * @returns {void}
   * @private
   */
  #renderCountText = () => {
    const hasQueue = this.state.queueCount > 0;

    const fn = hasQueue ? 'add' : 'remove';
    document.getElementById('type-btn').classList[fn]('is-danger');

    const htmlContent = this.#html[hasQueue ? 'hasQueue' : 'noQueue'];
    this.render(htmlContent, '.queueCount');
  };

  /**
   * @description accepts a html selector string
   * @param {string} selector
   */
  constructor(selector) {
    this.selector = selector;
  }

  /**
   * @description state of typer instance
   * @const {{queueCount: number, speed: number}}
   * @public
   */
  state = {
    queueCount: -1,
    speed: 30,
  };

  /**
   * @description appends a html string to the instance's html element
   * @param {string} html
   * @returns {void}
   * @public
   */
  append = (html) => {
    document.querySelector(this.selector).innerHTML += html;
  };

  /**
   * @description renders given html string inside element with given selector
   * @param {string} html
   * @param {string} [el]
   * @returns {void}
   * @public
   */
  render = (html, el = this.selector) => {
    document.querySelector(el).innerHTML = html;
  };
  /**
   * @description confirms existence of the instance's selector in the DOM
   * @returns {boolean}
   * @public
   */
  exists = () => !!document.querySelector(this.selector);

  /**
   * @description
   * - iterates through the passed string and calls
   *   the passed listener on each character in the string
   * - waits for the given time from the state's 'speed' property,
   *   before proceeding to the next iteration
   *
   * @param {string} string
   * @param {string} listener - function to call on each character of string
   * @returns {Promise<void>}
   *
   * @async
   * @public
   */
  handleIterateString = async (string, listener) => {
    for (let i of string) {
      listener(i);
      await this.#delay(this.state.speed);
    }
  };

  /**
   * @description increments the queue count in the state by one
   * @public
   * @returns {void}
   */
  incrementQueue = () => {
    this.state.queueCount++;
    this.#renderCountText();
  };

  /**
   * decrements the queue count in the state by one
   * @public
   * @returns {void}
   */
  decrementQueue = () => {
    this.state.queueCount--;
    this.#renderCountText();
  };
}

// instantiate demo
const demo = new Typer('.demo');

async function SetText(
  gg = `the puppy goes woof  woof woof woof...`,
  cmd = `sudo --info`
) {
  window.scrollTo({
    top: 0,
    behavior: 'smooth',
  });
  if (demo.exists()) {
    const html = `<code class="shell-session demo hljs nginx"><span class="hljs-attribute">Website</span> <span class="hljs-regexp">~ $</span> ${cmd}`;

    // render HTML container
    demo.render(html);

    // do typing
    await demo.handleIterateString(gg, demo.append);

    demo.decrementQueue();

    if (demo.state.queueCount >= 0) {
      SetText();
    }
  }
}

document.getElementById('type-btn').addEventListener('click', async () => {
  if (demo.state.queueCount === -1) {
    SetText();
  }
  demo.incrementQueue();
});
.form {
  display: flex;
  justify-content: space-between;
}

.select-box {
  display: flex;
  align-items: center;
}

label {
  margin-right: 10px;
  font-size: 0.8em;
}

.queueCount {
  min-height: 40px;
}

.demo {
  background: #000;
  color: #fff;
  min-height: 80px;
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.2/css/bulma.min.css">

<div class="container">
  <div class="form mb-3">
    <button class="button is-primary" id="type-btn">
    <span>Click me</span>
    </button>

    <div class="select-box">
      <label>speed per character (ms)</label>
      <div class="select">
        <select></select>
      </div>
    </div>
  </div>


  <div class="content is-normal mb-3">
    <div class="queueCount is-size-5"></div>
  </div>
  <div class="content is-normal">
    <div class="demo is-size-5"></div>
  </div>
</div>



<!-- demo select menu -->
<script>
  const select = document.querySelector('select');
  Array.from({
      length: 50,
    },
    (_, i) => (i + 1) * 30
  ).forEach((num) => {
    select.innerHTML += `<option value="${num}">${num}</option>`;
  });

  select.addEventListener('change', (e) => {
    demo.state.speed = parseInt(document.querySelector('select').value, 10);
  });
</script>


推荐阅读