Home > Back-end >  How to chain promises from different functions an unknown number of times (iterating an array)
How to chain promises from different functions an unknown number of times (iterating an array)

Time:03-06

I'm building a practice project from scratch. It's a vanilla javascript plugin that allows you to create an "animated typing effect".

Code pen with the demo so far: https://codepen.io/juan-sebasti-n-bonnett/pen/oNoVGad

const TypingEffect = {

  options: {
    parallel: false,
    showCursor: true,
    removeOtherCursors: true,
    removeCursorAfter: 0,
    cursorColor: '#000',
    cursorBG: '#000',
    cursorWeight: '0px',
    cursorInterval: 400,
    typingSpeed: 80,
    cursorChar: '|',
    eraseSpeed: 50,
    showcaseEraseDelay: 3000,
  },

  type: function(_elem, _str) {
    let elem;
    if (typeof(_elem) != 'string' && typeof(_elem) != 'object') return;
    if (typeof(_elem) == 'string') {
      console.log('we have to query a selector');
      elem = document.querySelector(_elem);
      console.log(elem);
    }
    if (this.options.showCursor) elem = this.cursor(elem);
    let typingSpeed = parseInt(this.options.typingSpeed);
    let str = _str || elem.innerHTML;
    let strArray = str.split('');
    let currentString = '';
    let i = 0;
    return new Promise((resolve, reject) => {
      let typing = setInterval(() => {
        if (i < strArray.length) {
          currentString = currentString   strArray[i];
          elem.innerHTML = currentString;
          i  ;
        } else {
          clearInterval(typing);
          resolve(elem);
        }
      }, typingSpeed);
    });
  },

  typeMultiple: function(elements) {
    let promise = Promise.resolve();
    elements.forEach(element => {
      promise = promise.then(() => {
        return this.type(element);
      });
    });
    promise.then(() => {
      if (parseInt(this.options.removeCursorAfter) > 0) {
        setTimeout(() => {
          this.removeCursor();
        }, this.options.removeCursorAfter);
      }
    });
    return promise;
  },

  typeSelector: function(selector) {
    let elements = document.querySelectorAll(selector);
    if (this.options.parallel) {
      elements.forEach(element => {
        this.type(element);
      });;
    } else {
      return this.typeMultiple(elements);
    }
  },

  erase: function(elem) {
    if (typeof(elem) != 'string' && typeof(elem) != 'object') return;
    if (typeof(elem) == 'string') {
      elem = document.querySelector(elem);
    }
    let str = elem.innerHTML;
    return new Promise((resolve, reject) => {
      let erasing = setInterval(() => {
        if (str.length > 0) {
          str = str.slice(0, -1);
          elem.innerHTML = str;
        } else {
          clearInterval(erasing);
          resolve(elem);
        }
      }, parseInt(this.options.eraseSpeed));
    });
  },

  cursor: function(elem) {
    if (this.options.removeOtherCursors) this.removeCursor();
    let string = elem.innerHTML;
    let cursorInterval = parseInt(this.options.cursorInterval);
    let cursorVisible = true;
    const cursorNode = document.createElement('span');
    const cursor = document.createTextNode(this.options.cursorChar);
    const stringNode = document.createElement('span');
    const stringContent = document.createTextNode(string);

    elem.innerHTML = '';

    stringNode.appendChild(stringContent);
    elem.appendChild(stringNode);
    stringNode.id = 'typing-string-content';

    cursorNode.appendChild(cursor);
    elem.appendChild(cursorNode);
    cursorNode.id = 'typing-cursor';
    cursorNode.style.paddingLeft = this.options.cursorWeight;
    cursorNode.style.backgroundColor = this.options.cursorBG;
    cursorNode.style.color = this.options.cursorColor;

    setInterval(() => {
      if (cursorVisible) {
        cursorNode.style.opacity = 0;
        cursorVisible = false;
      } else {
        cursorNode.style.opacity = 1;
        cursorVisible = true;
      }
    }, cursorInterval);

    return stringNode;
  },

  removeCursor: function(_cursor) {
    let cursor = _cursor || document.getElementById('typing-cursor');
    if (cursor) cursor.remove();
  },

  init: function(_options) {
    if (_options) Object.assign(this.options, _options);
  },

}
/*
Typing Effect usage
*/
let newOptions = {
  typingSpeed: 120,
  eraseSpeed: 120,
  cursorColor: 'rgb(14, 231, 32)',
}
TypingEffect.init(newOptions);

let string = `Hello! I'm using the type() function`;
let done = TypingEffect.type('#type-here', string);
done.then((elem) => {
  if (elem) {
    let notice = document.getElementById('notice');
    notice.innerHTML = `Done Typing! But I will be erased after 6 seconds`;
    setTimeout(() => {
      let doneErasing = TypingEffect.erase(elem);
      doneErasing.then((elem) => {
        notice.innerHTML = `Done erasing!`;
      });
    }, 6000);
  }
});
* {
  box-sizing: border-box;
}

body {
  background-color: #000;
}

.container {
  width: 100%;
  /*display: flex;
                align-items: center;
                justify-content: center;*/
}

.typing-effect {
  font-family: 'Consolas';
  color: rgb(14, 231, 32);
  font-size: 22px;
}

#notice {
  color: white;
  font-size: 22px;
  font-family: sans-serif;
  padding-top: 54px;
}
<div >
  <p id="type-here" ></p>
</div>
<div id="notice"></div>

I already have the mechanism that allows me to type a string inside an HTML element and another one that allows me to erase it. But my goal now is to be able to create a third function that receives an array of sentences and it starts typing a sentence, then waits a bit, erases it and then types the other sentence until the end of the array, then start over from index 0.

The code right now uses Promises and the logic goes as follows:

function type(selector, string) {
    // returns a promise that resolves 
    // when we're done typing the string
    // and returns the element that contains the text (the selector)
}

function erase(selector) {
    // returns a promise that resolves when
    // we're done erasing the whole node's content
    // and returns the element that is now empty (the selector)
}

Now, I'm still new to this concept of promises, and although they're easy to understand when they're explained with APIs and so, it gets hard to manipulate them in chain, mostly when you need something like this:

function typeMultipleSentences(selector, arrayOfSentences) {
    // LOOP:
    // types the first sentence of the array
    // when its done typing, waits for a couple seconds and then erases it
    // when it's done erasing, does the same with the next sentence in the array
}

//Usage 
typeMultipleSentences('#type-here', ['Hello World', 'Hola Mundo', 'Hallo Welt']);

Other functions inside the object I shared are just helpers and they do other kind of stuff with this effect, like adding the cursor to the end, typing paragraphs one after the other, etc. But those are not important, I only need to know how to achieve this kind of "Promise Chaining Delaying thing"

CodePudding user response:

It sounds like you need a little delay function, and you can do this by returning a promise that resolves after a set time, and then using async/await to simplify the code so you're not "chaining" anything, just waiting.

function delay(time) {
  return new Promise(res => {
    setTimeout(() => res(), time);
  });
}

async function main(data) {
  for (const el of data) {
    console.log(`Writing ${el}`);
    await delay(2000);
    console.log(`Erasing ${el}`);
    await delay(2000);
  }
  console.log('Done!');
}

const data = ['Hello World', 'Hola Mundo', 'Hallo Welt'];

main(data);

  • Related