Многие разработчики ограничиваются простым пониманием реализации промисов на поверхностном уровне, но лишь немногие из них углубляются во внутреннюю работу механизма. полное понимание их внутренней работы.

Я настоятельно рекомендую сначала понять синтаксис и работу реального обещания, прежде чем углубляться в эту статью.

Обратите внимание: эта реализация является всего лишь представлением фактического обещания, но может упускать из виду некоторые важные аспекты, такие как обещание.all(), обещание.allSettled(), обещание.race(), статические методы разрешения отклонения и, наконец.

Мы ограничиваем наши возможности следующим:

  • Конструктор обещаний
  • Состояние обещания
  • Решить/Отклонить
  • Затем
  • Выполнение обработчиков
  • Ловить

1. Состояние обещания

  • Обещание может иметь статус ОЖИДАЮЩЕЕ, РАЗРЕШЕННОЕ или ОТКЛОНЕННОЕ.
  • PENDING: когда обещание не было выполнено.
  • RESOLVED: когда обещание успешно разрешено.
  • REJECTED: когда обещание отбрасывается.
  • Мы будем поддерживать объект состояний как константу.
  • Если Promise еще не решен, его STATE.PENDING
  • В противном случае обещание будет иметь статус STATE.RESOLVED в случае успеха или STATE.REJECTED.

2. Конструктор обещаний

В конструкторе обещания мы определим четыре вещи:
1. Инициализация состояния: Инициализация состояния в ожидании, поскольку обещание не разрешается и не отклоняется.
2. Инициализируйте значение. Для обещания установлено значение null, оно будет установлено после того, как обещание будет разрешено или отклонено.
3. Иметь соответствующие обработчики, которые будут выполняться в ближайшем будущем асинхронно.
4. Запустить функцию исполнителя обратного вызова, чтобы обещание было выполнено. инициализированы и соответствующие состояния либо разрешены, либо отклонены.

3. Разрешить/Отклонить:

  • Всякий раз, когда мы запускаем обещание, его значение будет либо разрешено, либо отклонено.
  • Состояния отклоненного и разрешенного обещания уже завершили свой жизненный цикл, поэтому решение отклонено не вызывается.
  • Промис будет разрешен только в том случае, если он неразрешен (то есть в состоянии ожидания) и обещание выполнено успешно.
  • Когда обещание разрешено, состояние обновляется до решенного.
  • Значениям присваивается значение, переданное параметром разрешенной функции.

  • Всякий раз, когда мы запускаем обещание, его значение будет либо разрешено, либо отклонено.
  • Промис будет отклонен только в том случае, если он неразрешен (то есть в состоянии ожидания) и обещание не выполнено.
  • Состояния отклоненного и разрешенного обещания уже завершили свой жизненный цикл, поэтому решение отклонено не вызывается.
  • Когда обещание отклонено, его состояние меняется на «Отклонено».
  • Значения устанавливаются в значение, переданное параметром разрешенной функции/или функции catch.

3. Затем

  • then — это функция, которая продолжает выполнение обещания и в конечном итоге приводит к окончательному разрешению или отклонению.
  • Затем должно быть возможно объединение в цепочку, поскольку обещание, в свою очередь, может вернуть другое обещание.
  • Таким образом, then всегда должен возвращать новый экземпляр обещания.
  • Затем принимает два параметра: один — обратный вызов SuccessCall, другой — обратный вызов сбоя.
  • Всякий раз, когда срабатывает then, запускается новое обещание, и обработчикам передается объект, который принимает разрешение/отклонение нового объекта обещания, а также передает обратные вызовы успеха и неудачи.
  • После передачи значений обработчикам обработчики инициализируются (функция-исполнитель) в конструкторе нового обещания.
  • Следует понимать, что обработчик onSuccess фиксирует значение в своем замыкании при создании, а не при выполнении.
  • Поэтому, хотя обработчик является частью контекста нового обещания, он по-прежнему имеет доступ к значению из предыдущего обещания.
  • Таким образом, значение предыдущего промиса сохраняется при закрытии обработчика onSuccess, что позволяет получить к нему доступ даже при создании новых промисов.

4.Помощник обработчика выполнения:

  • Обработчики выполнения имеют два потока: один — при разрешении состояния, а другой — при отклонении потока.
  • Оба потока практически следуют одной и той же сигнатуре, с той лишь разницей, что при разрешении мы запускаем onSuccess, если он предусмотрен.
  • Принимая во внимание, что в случае отклонения мы запускаем обратный вызов onFail, если он предусмотрен.
  • Чтобы имитировать асинхронность, мы используем setTimeout, даже интервал времени 0 будет работать, поскольку мы просто хотим вывести эту функцию в очередь макрозадач (цикл событий). Вскоре я опубликую статью об очередях задач Eventloops, Micro и макросов.
  • Обработчик выполнения в основном проверяет следующие варианты использования:
  1. если onSuccess/onFail предоставил, он выполнил их со значением текущего обещания.
  2. если onSuccess/onFail не указан, просто разрешите отклонить новое обещание, созданное тогда, с текущим значением.
  3. если onSuccess/onFail возвращает новое обещание. выполнить это обещание тут же и передать решенное/отклоненное значение этого обещания в промежуточное обещание, созданное как часть then. это поддерживает цепочку.
  4. При любом исключении напрямую вызывается функция отклонения промежуточного обещания, созданного через then.

Подробные комментарии см. в коде внизу статьи.

Подробные комментарии см. в коде внизу статьи.

Уловка:

  1. В цепочке обещаний, когда в любом из предыдущих методов then возникает ошибка (например, создается исключение), нормальный поток цепочки нарушается.
  2. Цепочка обещаний переходит к ближайшему блоку catch в цепочке, пропуская все оставшиеся блоки then.
  3. Код внутри блока catch выполняется и получает ошибку в качестве аргумента, что позволяет вам обработать или зарегистрировать ошибку по мере необходимости.
  4. После выполнения блока catch цепочка обещаний продолжает свой обычный поток, а это означает, что вы все равно можете присоединить дополнительные блоки then или catch после блока catch для обработки последующих обещаний.

Собираем все вместе:

const state = {
    PENDING: 'pending', 
    RESOLVED:'resolved',
    REJECTED: 'rejected'
}

class CustomPromise {
    constructor(executor){
        // executor is the callback which is initialized when a promise is created.
        this.value = null;
        this.handlers = [];
        this.state = state.PENDING;
        
        try{
            // execute the executor with resolve and rejects as params.
            executor(this.resolve.bind(this), this.reject.bind(this))
        }catch(e){
            this.reject(e)
        }
    }
    
    resolve(value){
        // only resolve when unresolved
        if(this.state ===state.PENDING){
            this.state = state.RESOLVED;
        //update value to the resolved value
            this.value = value;
            this.handlers.forEach(item => item.onSuccess(this.value))
        }
    }
    
    reject(value){
        // only reject when unresolved
        if(this.state=== state.PENDING){
            this.state = state.REJECTED;
            // update value to the rejected value
            this.value = value;
            this.handlers.forEach(item => item.onFail(this.value))
        }
    }
    
    executeHandlers(handleObj){
 //handle the resolved flow
        if(this.state === state.RESOLVED){
           var cb = handleObj.onSuccess
           // to mock asynchrony
           setTimeout(() => {
               try{
                  // check if success callback present
                   if(typeof cb === "function"){
                  // if yes then execute the cb with the existing promise resolved value
                    var res = cb(this.value)
                  // if the result is inturn returns a promise
                    if(res instanceof CustomPromise){
                  // resolve the promise and set the value to returning promise.
                        res.then(resVal => handleObj.res(resVal), resVal => handleObj.rej(resVal))
                    } else {
                        setTimeout(() => {
                    // if res is not a promise directly resolve it withe the promise create from the previous then
                            handleObj.res(res)
                        }, 0)
                    }
               }else {
// if success callback not present simply resolve the promise with the result of callback
                   handleObj.res(this.value)
               }
               }catch(e){
// on any exception trigger the reject handler.
                  handleObj.rej(e) 
               }
           })
//handle the rejected flow below
        } else if(this.state === state.REJECTED){
            // to mock asynchrony
            var cb = handleObj.onFail;
            setTimeout(() => {
                try{
                  // check if failure callback present
                    if(typeof cb === "function"){
// if yes then execute the cb with the existing promise resolved value
                        const res = cb(this.value)
// if the result is inturn returns a promise
                        if(res instanceof CustomPromise){
                            res.then(resVal => handleObj.res(resVal), resVal => handleObj.rej(resval))
                        } else {
                            setTimeout(() => {
// if res is not a promise directly reject it withe the promise create from the previous then
                                 handleObj.rej(res)
                        }, 0)
                        }
                    } else{
// if failure callback not present simply resolve the promise with the result of callback
                        handleObj.rej(this.value)
                    }
                } catch(e){
// on any exception trigger the reject handler.
                   handleObj.rej(e)   
                }
            })
        }
    }
    
    then(onSuccess, onFail){
// everytime a then is triggerred return a new promise
        return new CustomPromise((res, rej) => {
// as a part of constructor function push handlers with new resolve , reject and prev onSuccess. onFail
            this.handlers.push({
                res,
                rej,
                onSuccess,
                onFail
            })
//execute the handlers to kickstart
            this.executeHandlers(this.handlers[this.handlers.length-1])
        })
    }
    
    catch(onFail) {
// onany failure / exception gracefully handle the scenarios
        return this.then(null, onFail);
    }
}

let a = new CustomPromise((res, rej) => {
    
    res(5)
})

a.then((val) => {
    //createdNew promise with old new resolve reject and new onSuccess onFail function
    console.log(val)
    return new CustomPromise((res, rej) => {
        res(10)
    })
}).then(val => {
    console.log(val)
})

  • В приведенном выше примере мы разрешаем обещание со значением 5 как часть его конструктора, где срабатывает разрешение.
  • Resolve устанавливает значение 5 и состояние как решенное.
  • Сначала then мы возвращаем новое пользовательское обещание.
  • Однако при создании этого нового пользовательского обещания выполняется внутренний функционал конструктора и выполняется функция ExecutHandler.
  • Так как в данном случае мы возвращаем еще один кастомный промис.
  • следовательно, он попадает в сценарий обратного вызова, возвращающего пользовательское обещание, поэтому мы выполним пользовательское обещание с разрешенным значением 10).
  • При выполнении этого нового пользовательского обещания мы устанавливаем значение 10.
  • При следующем триггере then мы просто запускаем новое обещание, а затем с предыдущим значением обещания.
  • Вот как мы урегулируем все ценности.

Заключение

Я знаю, что это была непростая задача. Однако Promises необходимы для понимания асинхронности, и этот небольшой проект, безусловно, поможет нам расти как разработчикам.

Изучая эти фундаментальные аспекты обещаний, читатели получают ценную информацию о том, как обещания работают внутри компании. Важно отметить, что хотя эта реализация специального обещания обеспечивает упрощенное представление обещаний, реальные обещания предлагают дополнительные функции, такие как Promise.all(), Promise.race() и статические методы, такие как Promise.resolve() и Promise.reject(). Тем не менее, понимание основных концепций, представленных здесь, формирует прочную основу для понимания внутренней работы промисов в JavaScript.