進捗・どう・です・か

元ネタ：[「進捗・どう・です・か」をランダムに表示し「進捗どうですか」が完成したら煽ってくるプログラムをES６で書いてみる](https://qiita.com/Nossa/items/d619c7f88e69ab205241)

メッセージがリアルタイムに伸びていくようにしてみた。

- TypeScript で書いたが、関数の引数と返値に型情報を追加しただけなので、「JavaScript は分かるけど TypeScript は分からない」という方でも（`: string` とか `: number` とかを無視していただければ）理解できるはず。
- コンソール出力だと改行なしにはできなさそうなので、HTML に出力するようにした。
- async/await で遅延処理を入れることで徐々にメッセージが伸びるようにした。
- CodePen を埋め込んでみたが、一定時間経つと処理が止まってしまう（最後まで行かないことがある）ようだ。

```JavaScript
const WORDS = Object.freeze(["進捗", "どう", "です", "か"]);
const SEQUENCE_FINISH = "???";
const SHOUT = `＿人人人人人人人＿
＞進捗どうですか＜
￣Y^Y^Y^Y^Y^Y^Y￣`;

const SEQUENCE_INTERVAL = 10;
const SHOUT_DELAY = 1500;

const SEQUENCE_CONTAINER_ID = "sequence_container";
const SHOUT_CONTAINER_ID = "shout_container";


(async () => {
    await appendWordSequenceAsync(
        WORDS, SEQUENCE_CONTAINER_ID, SEQUENCE_INTERVAL);
    appendText(SEQUENCE_CONTAINER_ID, SEQUENCE_FINISH);
    await timeoutAsync(SHOUT_DELAY);
    appendMultilineText(SHOUT_CONTAINER_ID, SHOUT);
})();


async function appendWordSequenceAsync(
    words: ReadonlyArray<string>,
    parentElementId: string,
    interval: number
): Promise<void> {
    let progress = 0;
    while (true) {
        const index = getRandomInt(0, words.length);

        const word = words[index];
        appendText(parentElementId, word);

        if (progress !== index) {
            progress = 0;
        }

        if (progress === index) {
            ++progress;
            if (progress >= words.length) {
                break;
            }
        }

        await timeoutAsync(interval);
    }
}

function appendMultilineText(
    parentElementId: string,
    text: string
): void {
    text.split("\n").forEach(line => {
        appendText(parentElementId, line);
        appendBr(parentElementId);
    });
}

function appendText(
    parentElementId: string,
    text: string
): void {
    const parent = ensureElement(parentElementId);
    const textNode = document.createTextNode(text);
    parent.appendChild(textNode);
}

function appendBr(parentElementId: string): void {
    const br = document.createElement("br");
    ensureElement(parentElementId).append(br);
}

function ensureElement(elementId: string): HTMLElement {
    const existing = document.getElementById(elementId);
    if (existing) { return existing; }

    const newElement = document.createElement("div");
    newElement.id = elementId;
    document.body.appendChild(newElement);
    return newElement;
}

function getRandomInt(
    rangeBegin: number,
    rangeEnd: number
): number {
    return rangeBegin
        + Math.floor(Math.random() * (rangeEnd - rangeBegin));
}

function timeoutAsync(delay: number): Promise<void> {
    return new Promise(resolve => 
        setTimeout(() => resolve(), delay)
    );
}
```

[Playground · TypeScript](https://www.typescriptlang.org/play/#src=const%20WORDS%20%3D%20Object.freeze(%5B%22進捗%22%2C%20%22どう%22%2C%20%22です%22%2C%20%22か%22%5D)%3B%0Aconst%20SEQUENCE_FINISH%20%3D%20%22%3F%3F%3F%22%3B%0Aconst%20SHOUT%20%3D%20%60＿人人人人人人人＿%0A＞進捗どうですか＜%0A￣Y%5EY%5EY%5EY%5EY%5EY%5EY￣%60%3B%0A%0Aconst%20SEQUENCE_INTERVAL%20%3D%2010%3B%0Aconst%20SHOUT_DELAY%20%3D%201500%3B%0A%0Aconst%20SEQUENCE_CONTAINER_ID%20%3D%20%22sequence_container%22%3B%0Aconst%20SHOUT_CONTAINER_ID%20%3D%20%22shout_container%22%3B%0A%0A%0A(async%20()%20%3D%3E%20%7B%0A%20%20%20%20await%20appendWordSequenceAsync(%0A%20%20%20%20%20%20%20%20WORDS%2C%20SEQUENCE_CONTAINER_ID%2C%20SEQUENCE_INTERVAL)%3B%0A%20%20%20%20appendText(SEQUENCE_CONTAINER_ID%2C%20SEQUENCE_FINISH)%3B%0A%20%20%20%20await%20timeoutAsync(SHOUT_DELAY)%3B%0A%20%20%20%20appendMultilineText(SHOUT_CONTAINER_ID%2C%20SHOUT)%3B%0A%7D)()%3B%0A%0A%0Aasync%20function%20appendWordSequenceAsync(%0A%20%20%20%20words%3A%20ReadonlyArray%3Cstring%3E%2C%0A%20%20%20%20parentElementId%3A%20string%2C%0A%20%20%20%20interval%3A%20number%0A)%3A%20Promise%3Cvoid%3E%20%7B%0A%20%20%20%20let%20progress%3A%20number%20%3D%200%3B%0A%20%20%20%20while%20(true)%20%7B%0A%20%20%20%20%20%20%20%20const%20index%20%3D%20getRandomInt(0%2C%20words.length)%3B%0A%0A%20%20%20%20%20%20%20%20const%20word%3A%20string%20%3D%20words%5Bindex%5D%3B%0A%20%20%20%20%20%20%20%20appendText(parentElementId%2C%20word)%3B%0A%0A%20%20%20%20%20%20%20%20if%20(progress%20!%3D%3D%20index)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20progress%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20if%20(progress%20%3D%3D%3D%20index)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2B%2Bprogress%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(progress%20%3E%3D%20words.length)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20await%20timeoutAsync(interval)%3B%0A%20%20%20%20%7D%0A%7D%0A%0Afunction%20appendMultilineText(%0A%20%20%20%20parentElementId%3A%20string%2C%0A%20%20%20%20text%3A%20string%0A)%3A%20void%20%7B%0A%20%20%20%20text.split(%22%5Cn%22).forEach(line%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20appendText(parentElementId%2C%20line)%3B%0A%20%20%20%20%20%20%20%20appendBr(parentElementId)%3B%0A%20%20%20%20%7D)%3B%0A%7D%0A%0Afunction%20appendText(%0A%20%20%20%20parentElementId%3A%20string%2C%0A%20%20%20%20text%3A%20string%0A)%3A%20void%20%7B%0A%20%20%20%20const%20parent%20%3D%20ensureElement(parentElementId)%3B%0A%20%20%20%20const%20textNode%20%3D%20document.createTextNode(text)%3B%0A%20%20%20%20parent.appendChild(textNode)%3B%0A%7D%0A%0Afunction%20appendBr(parentElementId%3A%20string)%3A%20void%20%7B%0A%20%20%20%20const%20br%20%3D%20document.createElement(%22br%22)%3B%0A%20%20%20%20ensureElement(parentElementId).append(br)%3B%0A%7D%0A%0Afunction%20ensureElement(elementId%3A%20string)%3A%20HTMLElement%20%7B%0A%20%20%20%20const%20existing%20%3D%20document.getElementById(elementId)%3B%0A%20%20%20%20if%20(existing)%20%7B%20return%20existing%3B%20%7D%0A%0A%20%20%20%20const%20newElement%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20newElement.id%20%3D%20elementId%3B%0A%20%20%20%20document.body.appendChild(newElement)%3B%0A%20%20%20%20return%20newElement%3B%0A%7D%0A%0Afunction%20getRandomInt(%0A%20%20%20%20rangeBegin%3A%20number%2C%0A%20%20%20%20rangeEnd%3A%20number%0A)%3A%20number%20%7B%0A%20%20%20%20return%20rangeBegin%0A%20%20%20%20%20%20%20%20%2B%20Math.floor(Math.random()%20*%20(rangeEnd%20-%20rangeBegin))%3B%0A%7D%0A%0Afunction%20timeoutAsync(delay%3A%20number)%3A%20Promise%3Cvoid%3E%20%7B%0A%20%20%20%20return%20new%20Promise(resolve%20%3D%3E%20%0A%20%20%20%20%20%20%20%20setTimeout(()%20%3D%3E%20resolve()%2C%20delay)%0A%20%20%20%20)%3B%0A%7D%0A%0A)

<p class="codepen" data-height="265" data-theme-id="0" data-default-tab="js,result" data-user="sdkei" data-slug-hash="bJzMzW" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid black; margin: 1em 0; padding: 1em;" data-pen-title="進捗どうですか？？？">
  <span>See the Pen <a href="https://codepen.io/sdkei/pen/bJzMzW/">
  進捗どうですか？？？</a> by sdkei (<a href="https://codepen.io/sdkei">@sdkei</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>