// класс с константами 
class CustomPromiseStatus {
  static Pending = `pending`;
  static Fulfilled = `fulfilled`;
  static Rejected = `rejected`;
}

// сам промис
class CustomPromise {
  #callbacks = new Set(); // сюда будем помещать все каллбеки передаваемые в качестве аргумента метода .then()
  #status = CustomPromiseStatus.Pendind; // поле хранящее одно из трех возможных значений состояния
  #errorCallback = null; // callback который разработчик передает в метод catch 
  
  get status(){ // доступ к полю status
    return this.#status;
  }


constructor(executor){ // executer это та самая фунекция-аргумент передаваемая в конструктор new Promise( (resolve, reject  ) => { /** я и ест executer */} )
  
  
  // эта функция передается в экзекутер в качестве первого аргумента и вызывается при завершении асинхронных операций разрработчиком
  const resolve = data => {
    this.#status = CustomPromiseStatus.Fulfilled; // меняем статус промиса поскольку разработчик подтвердил успешное выполнение асинхронных операций
    
    let callbacks = Array.from(this.#callbacks); // клонируем массив с каллбеками переданных с помощью цепочки методов .then().then()
    const isThenable = value => value.then !== undefined; // валидатор проверяющий на принадлежность к thenable
    
    // функция рекурсивного асинхронного вызова калбеков                           
    const recursive = (callbacks, data) => {
      let callback = callbacks.shift(); // удаляем и получаем первый каллбек
      
      if(!callback){ // если каллбеков больше не осталось возвращаем значение
        return data;
      }
      
      let result = callback(data); // вызываем каллбек с данными в качестве аргумента и сохраняем возращенное значение в переменную result 
  
      // тут проверяем что если result это промис, то ожидаем его завершения иначе продолжаем рекурсивную итерацию
      return result && isThenable(result) ?
        result.then( data => recursive(callbacks, data) ) : 
        recursive(callbacks, result);
      
    }
    
    recursive(callbacks, data); // начинаем итерацию
  };
  
  // эта функция передается в экзекутер в качестве второго аргумента и вызывается при звозникновении ошибки разработчиком
  const reject = error => {
    // первым делом изменяем статус
   this.#status = CustomPromiseStatus.Rejected;
   
   // вызываем метод catch с ошибков в качестве аргумента 
   this.#errorCallback(error);
  };
  
  setTimeout(()=> executor(resolve,reject),0); // откладываем вызов экзекутара на следующий тик поскольку необходимо сохранить функци обратного вызова передаваемые в качестве аргумента в метод .then доступ к которым возможен только после завершения создания объекта просмиса
}
  
  then(callback){
    this.#callbacks.add(callback);
    
    return this;
  }

  catch(errorCallback){
    this.#errorCallback = errorCallback;
  }
}

new CustomPromise((resolve, reject) => {
  setTimeout(()=>resolve(`executor__`), 1000);
})
  .then( data => data + `first-then__` )
  .then( data => new CustomPromise((resolve, reject) => setTimeout(()=> resolve(data + `then-with-promise`)), 1000) )
  .then(console.log);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.