Construindo uma Promise do zero
Tags: typescript, javascript, node
Published: 2019-08-21 23:09:26

Há um tempo atrás publiquei um artigo sobre [como podemos entender Promises de uma vez por todas](https://dev.to/khaosdoctor/entendendo-promises-de-uma-vez-por-todas-44i7), se você ainda não leu, recomendo a leitura para que possamos continuar, mas vou dar um pequeno resumo do que conversamos nele.

Promises são estruturas que lidam com valores que podem ser obtidos futuramente em uma execução, por exemplo, uma requisição para um servidor externo, ou então uma leitura de um arquivo. O que poucos sabem é que, de fato, a Promise é um padrão de projeto que pode ser implementado utilizando orientação a objetos e a especificação descrita no [PromisesA+](https://promisesaplus.com/).

Como a melhor forma de aprender é fazendo, vamos mergulhar na especificação das Promises e vamos implementar nossa própria Promise do zero!

## Especificação

A especificação de Promises como conhecemos no JavaScript está no Promises/A+ (uma evolução do [Promises/A](http://wiki.commonjs.org/wiki/Promises/A)), pois, antes da implementação nativa, algumas bibliotecas como o Q e o Bluebird já implementavam este padrão. Então resolveu-se criar uma especificação aberta onde as pessoas que implementavam este modelo pudessem escrever e debater estes assuntos com outros programadores. Esta especificação define, basicamente, como o método `then` deve funcionar, de forma que todas as Promises que conformem com ela devem funcionar da mesma maneira em qualquer lugar.

### Terminologia

Vamos dar nome a algumas coisas, primeiramente vamos definir todos os termos que vamos utilizar na nossa promise, isto vem de uma tradução direta da especificação:

- A `Promise` é um objeto com um método `then` cujo comportamento conforma com esta especificação
- Um `thenable` é um objeto ou função que define um método `then`
- Um **valor** é qualquer valor válido no JavaScript (incluindo `undefined`, um `thenable` ou até outra `promise`)
- Uma `exception` é uma exceção padrão de desenvolvimento que é levantada a partir de um `throw`
- A **razão** é o motivo pelo qual uma `promise` foi rejeitada (quando sofre uma `exception`)

### Estado

A Promise é essencialmente uma máquina de estados. Ela pode estar em um de três possíveis estados:

- **Pendente:** Neste estado ela pode ir para `fulfilled` ou `rejected`
- **Fulfilled (completa):** Neste estado, a promise não pode transicionar para nenhum outro estado; Também deve possuir um *valor* que **não** deve ser alterável
- **Rejected (rejeitada):** Neste estado, a promise não pode transicionar para nenhum outro estado; Também deve possuir uma *razão* que **não** deve ser alterável

### Then

Todas as Promises precisam especificar um método `then` que vai ser o responsável por, de fato, avaliar a função e retornar o valor atual. Todo método `then` deve ter a seguinte assinatura:

```js
promise.then(onFulfilled, onRejected)
```

Onde, `onFulfilled` é uma função com a seguinte assinatura:

```js
(value: T) => void
```

E `onRejected` tem a mesma assinatura, porém com uma **razão** ao invés de um valor.

Além disso, o `then` precisa seguir uma série de regras para poder ser considerado conforme com a especificação. Não vou colocar todas elas aqui, mas vou incluir as mais importantes:

- Tanto `onFulfilled` quanto `onRejected` são parâmetros opcionais para o `then` e devem ser ignorados se não forem funções
- `onFulfilled`, quando aceito, deve ser chamado **sempre** após a promise ter sido resolvida, com o valor da promise como primeiro argumento. Além disso ele só pode ser chamado uma vez.
- `onRejected`, quando aceito, deve ser chamado **sempre** após a promise ter sido rejeitada, com a razão da promise como primeiro argumento. Além disso ele só pode ser chamado uma vez.
- `then` pode ser encadeado múltiplas vezes na mesma promise. Quando a promise é completada ou rejeitada, todos os *handlers* `then` devem ser executados em ordem.
- `then` deve retornar outra promise

## Implementação

Para começarmos a implementar nossa promise, vamos primeiro criar um arquivo chamado `PromiseTypes.ts`, vamos usar Typescript para poder fazer com que ela tenha alguns tipos que vão deixar muito mais simples o entendimento. Neste arquivo vamos colocar os tipos globais, que já sabemos que existem, como a função `onFulfilled` e `onRejected`, executores e tudo mais.

Vamos começar criando um enumerador com os estados possíveis de uma promise:

```ts
export enum PromiseStates {
  PENDING,
  FULFILLED,
  REJECTED
}
```

Agora vamos criar os tipos de apoio e os tipos base da promise que vamos usar:

```ts
export type ResolveFunction = (value: any) => any
export type RejectFunction = (reason: any) => any
export type Thennable = { then: (value: any) => TypePromise }
export type ExecutorFunction = (resolve: ResolveFunction, reject: RejectFunction) => void
```

Estamos chamando de `executor` a função que a Promise irá receber em seu construtor, ela que deverá conter o `resolve` e o `reject`. Da mesma forma, criamos um tipo para o `thennable`. Vamos também criar um outro tipo auxiliar só para que possamos deixar nosso código mais bonito, chamado `nullable<T>`, ele vai servir para podermos implementar elementos que podem ser nulos:

```ts
export type Nullable<T> = T | null
```

### Máquina de estados

Vamos começar criando um arquivo chamado `TypePromise.ts`, vamos chamar nossa classe de `TypePromise` para não conflitar com a implementação nativa de Promises, por enquanto ela é uma simples máquina de estados, considerando todos os estados que temos que ter:

```ts
import { PromiseStates, ResolveFunction, RejectFunction, ExecutorFunction, Nullable, Thennable } from './PromiseTypes'

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
}
```

Veja que temos um tipo novo, as `HandlerFunctions` são objetos que vamos passar quando tivermos diversos `then` ou `catch` em nossa promise. Nestas situações temos que executar os handlers um por um. Cada um deles é um objeto com as duas funções do executor, vamos adicionar no nosso arquivo `PromiseTypes.ts` e importar no arquivo principal, nosso arquivo `PromiseTypes.ts` fica assim:

```ts
import { TypePromise } from './TypePromise'

export enum PromiseStates {
  PENDING,
  FULFILLED,
  REJECTED
}

export type ResolveFunction = (value: any) => any
export type RejectFunction = (reason: any) => any
export type ExecutorFunction = (resolve: ResolveFunction, reject: RejectFunction) => void
export type Thennable = { then: (value: any) => TypePromise }

export type Nullable<T> = T | null

export type HandlerFunction = {
  onFulfilled?: ResolveFunction;
  onRejected?: Nullable<RejectFunction>
}
```

Agora vamos transicionar nossa promise para os dois valores conhecidos, `FULFILLED` e `REJECTED`:

```ts
import { PromiseStates, ResolveFunction, RejectFunction, ExecutorFunction, Nullable, Thennable, HandlerFunction } from './PromiseTypes'

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
  }
}
```

Veja que as transições nada mais são do que alteradores de estado. São eles que vão finalizar uma promise e setar seu valor final.

Vamos agora criar uma outra transição chamada de `resolve`, esta transição será responsável por executar a promise em si e definir se ela foi resolvida ou rejeitada, bem como tratar se nossa promise receber outra promise.

```ts
import { PromiseStates, ResolveFunction, RejectFunction, ExecutorFunction, Nullable, Thennable, HandlerFunction } from './PromiseTypes'

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
  }
  
  private resolve (result: any) {
  	try {
  		const then = this.getThen(result)
  		if (then) return this.doResolve(then.bind(result), this.resolve, this.reject)
  		this.fulfill(result)
  	} catch (error) {
  		this.reject(error)
  	}
  }
}
```

Nossa função `resolve` é basicamente a responsável por saber se a função que recebemos é um objeto `thennable`, se sim, então irá chamar uma nova função chamada `doResolve` que leva um executor e faz um proxy da execução para os métodos internos da nossa própria promise, que serão chamados uma vez que a promise for resolvida, em poucas palavras, esta função é responsável por esperar que uma possível promise interna seja resolvida, uma vez que uma promise não pode ser resolvida com outra promise. Vamos implementar primeiramente o `getThen`, que é o responsável por extrair ou ignorar uma função `thennable`:

```ts
import { PromiseStates, ResolveFunction, RejectFunction, ExecutorFunction, Nullable, Thennable, HandlerFunction } from './PromiseTypes'

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
  }
  
  private resolve (result: any) {
  	try {
  		const then = this.getThen(result)
  		if (then) return this.doResolve(then.bind(result), this.resolve, this.reject)
  		this.fulfill(result)
  	} catch (error) {
  		this.reject(error)
  	}
  }
  
  private getThen (value: Thennable) {
    const valueType = typeof value
    if (value && (valueType === 'object' || valueType === 'function')) {
      const then = value.then
      if (typeof then === 'function') return then
    }
    return null
  }
}
```

É uma função bem simples, ela recebe um objeto `thennable` e verifica se o tipo do valor passado é um objeto ou uma função, caso seja – e possua uma propriedade `then` – então verificamos se esta propriedade é uma função para podermos retornar somente este handler.

Vamos para o método `doResolve`, ele é o método principal da promise, porque é ele que vai iniciar toda a cadeia. Sendo responsável por garantir a única execução e também por criar wrappers ao redor das funções passadas pelo usuário para que elas possam ser controladas internamente.

```ts
import { 
  PromiseStates, 
  ResolveFunction, 
  RejectFunction, 
  ExecutorFunction, 
  Nullable, 
  Thennable, 
  HandlerFunction 
} from './PromiseTypes'

enum ReturnType {
  SUCCESS = 'success',
  ERROR = 'error'
}

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
  }
  
  private resolve (result: any) {
  	try {
  		const then = this.getThen(result)
  		if (then) return this.doResolve(then.bind(result), this.resolve, this.reject)
  		this.fulfill(result)
  	} catch (error) {
  		this.reject(error)
  	}
  }
  
  private getThen (value: Thennable) {
    const valueType = typeof value
    if (value && (valueType === 'object' || valueType === 'function')) {
      const then = value.then
      if (typeof then === 'function') return then
    }
    return null
  }
  
  private getHandlerType (type: ReturnType, done: boolean, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    return (value: any) => {
      if (done) return
      done = true
      return { error: onRejected, success: onFulfilled }[type](value)
    }
  }
  
  private doResolve (resolverFn: ExecutorFunction, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    let done = false
    try {
			resolverFn(this.getHandlerType(ReturnType.SUCCESS, done, onFulfilled, onRejected), this.getHandlerType(ReturnType.ERROR, done, onFulfilled, onRejected))
    } catch (error) {
      if (done) return
      done = true
      onRejected(error)
    }
  }
}
```

Agora fizemos algumas coisas a mais. Primeiramente nossa função `doResolve` basicamente checa se o nosso executor é confiável, ela faz isso criando wrappers ao redor das funções internas que temos para que elas somente sejam executadas uma única vez, isto é feito na nossa função `getHandlerType` que, basicamente, retorna uma nova função com assinatura `(value: any)` com uma checagem se ela já foi ou não executada alguma vez. Se sim, apenas retornamos, se não, vamos pegar a respectiva função de acordo com o tipo que queremos – ela pode ser uma função de resolução ou de rejeição, para fazer isso criamos no topo do arquivo o enum interno `ReturnType` – e vamos executá-la retornando seu valor.

Por fim, vamos fazer uma pequena alteração em nossa resolução de promises, de forma que possamos criar uma cadeia de promises a serem resolvidas. Para isso, vamos criar um método chamado `executeHandler`, que será responsável por receber e executar um objeto `HandlerFunction`, e a função auxiliar `executeAllHandlers` que irá iterar pelo nosso array de handlers e executar todos até que não haja mais nenhum:

```ts
import { 
  PromiseStates, 
  ResolveFunction, 
  RejectFunction, 
  ExecutorFunction, 
  Nullable, 
  Thennable, 
  HandlerFunction 
} from './PromiseTypes'

enum ReturnType {
  SUCCESS = 'success',
  ERROR = 'error'
}

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
		this.executeAllHandlers()
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
    this.executeAllHandlers()
  }
  
  private executeAllHandlers () {
    this.thenHandlers.forEach(this.executeHandler)
    this.thenHandlers = []  
  }
  
  private executeHandler (handler: HandlerFunction) {
    if (this.state === PromiseStates.PENDING) return this.thenHandlers.push(handler)
    if (this.state === PromiseStates.FULFILLED && typeof handler.onFulfilled === 'function') return handler.onFulfilled(this.value)
    if (this.state === PromiseStates.REJECTED && typeof handler.onRejected === 'function') return handler.onRejected(this.value)
  }
  
  private resolve (result: any) {
  	try {
  		const then = this.getThen(result)
  		if (then) return this.doResolve(then.bind(result), this.resolve, this.reject)
  		this.fulfill(result)
  	} catch (error) {
  		this.reject(error)
  	}
  }
  
  private getThen (value: Thennable) {
    const valueType = typeof value
    if (value && (valueType === 'object' || valueType === 'function')) {
      const then = value.then
      if (typeof then === 'function') return then
    }
    return null
  }
  
  private getHandlerType (type: ReturnType, done: boolean, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    return (value: any) => {
      if (done) return
      done = true
      return { error: onRejected, success: onFulfilled }[type](value)
    }
  }
  
  private doResolve (resolverFn: ExecutorFunction, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    let done = false
    try {
			resolverFn(this.getHandlerType(ReturnType.SUCCESS, done, onFulfilled, onRejected), this.getHandlerType(ReturnType.ERROR, done, onFulfilled, onRejected))
    } catch (error) {
      if (done) return
      done = true
      onRejected(error)
    }
  }
}
```

Com isso terminamos nossa máquina de estados.

### Expondo métodos

Já que acabamos de construir nossa máquina de estados e suas transições, vamos agora criar os métodos que vão poder ser executados pelo usuário, como o `then`, `catch` e etc. Primeiramente vamos adicionar uma forma de resolver uma promise, criando seu construtor:

```ts
import { 
  PromiseStates, 
  ResolveFunction, 
  RejectFunction, 
  ExecutorFunction, 
  Nullable, 
  Thennable, 
  HandlerFunction 
} from './PromiseTypes'

enum ReturnType {
  SUCCESS = 'success',
  ERROR = 'error'
}

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
 
  constructor (executor: ExecutorFunction) {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
    this.executeHandler = this.executeHandler.bind(this)
    this.doResolve(executor, this.resolve, this.reject)
  }
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
		this.executeAllHandlers()
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
    this.executeAllHandlers()
  }
  
  private executeAllHandlers () {
    this.thenHandlers.forEach(this.executeHandler)
    this.thenHandlers = []  
  }
  
  private executeHandler (handler: HandlerFunction) {
    if (this.state === PromiseStates.PENDING) return this.thenHandlers.push(handler)
    if (this.state === PromiseStates.FULFILLED && typeof handler.onFulfilled === 'function') return handler.onFulfilled(this.value)
    if (this.state === PromiseStates.REJECTED && typeof handler.onRejected === 'function') return handler.onRejected(this.value)
  }
  
  private resolve (result: any) {
  	try {
  		const then = this.getThen(result)
  		if (then) return this.doResolve(then.bind(result), this.resolve, this.reject)
  		this.fulfill(result)
  	} catch (error) {
  		this.reject(error)
  	}
  }
  
  private getThen (value: Thennable) {
    const valueType = typeof value
    if (value && (valueType === 'object' || valueType === 'function')) {
      const then = value.then
      if (typeof then === 'function') return then
    }
    return null
  }
  
  private getHandlerType (type: ReturnType, done: boolean, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    return (value: any) => {
      if (done) return
      done = true
      return { error: onRejected, success: onFulfilled }[type](value)
    }
  }
  
  private doResolve (resolverFn: ExecutorFunction, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    let done = false
    try {
			resolverFn(this.getHandlerType(ReturnType.SUCCESS, done, onFulfilled, onRejected), this.getHandlerType(ReturnType.ERROR, done, onFulfilled, onRejected))
    } catch (error) {
      if (done) return
      done = true
      onRejected(error)
    }
  }
}
```

Veja que nosso construtor simplesmente seta os valores iniciais e chama a função `doResolve`, dando inicio à resolução da promise.

### Observando os valores

Para observarmos uma promise utilizamos `then`, para casos de sucesso, ou `catch` para casos de erro. Vamos criar nosso primeiro método público `then`:

```ts
import { 
  PromiseStates, 
  ResolveFunction, 
  RejectFunction, 
  ExecutorFunction, 
  Nullable, 
  Thennable, 
  HandlerFunction 
} from './PromiseTypes'

enum ReturnType {
  SUCCESS = 'success',
  ERROR = 'error'
}

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
 
  constructor (executor: ExecutorFunction) {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
    this.executeHandler = this.executeHandler.bind(this)
    this.doResolve(executor, this.resolve, this.reject)
  }
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
		this.executeAllHandlers()
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
    this.executeAllHandlers()
  }
  
  private executeAllHandlers () {
    this.thenHandlers.forEach(this.executeHandler)
    this.thenHandlers = []  
  }
  
  private executeHandler (handler: HandlerFunction) {
    if (this.state === PromiseStates.PENDING) return this.thenHandlers.push(handler)
    if (this.state === PromiseStates.FULFILLED && typeof handler.onFulfilled === 'function') return handler.onFulfilled(this.value)
    if (this.state === PromiseStates.REJECTED && typeof handler.onRejected === 'function') return handler.onRejected(this.value)
  }
  
  private resolve (result: any) {
  	try {
  		const then = this.getThen(result)
  		if (then) return this.doResolve(then.bind(result), this.resolve, this.reject)
  		this.fulfill(result)
  	} catch (error) {
  		this.reject(error)
  	}
  }
  
  private getThen (value: Thennable) {
    const valueType = typeof value
    if (value && (valueType === 'object' || valueType === 'function')) {
      const then = value.then
      if (typeof then === 'function') return then
    }
    return null
  }
  
  private getHandlerType (type: ReturnType, done: boolean, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    return (value: any) => {
      if (done) return
      done = true
      return { error: onRejected, success: onFulfilled }[type](value)
    }
  }
  
  private doResolve (resolverFn: ExecutorFunction, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    let done = false
    try {
			resolverFn(this.getHandlerType(ReturnType.SUCCESS, done, onFulfilled, onRejected), this.getHandlerType(ReturnType.ERROR, done, onFulfilled, onRejected))
    } catch (error) {
      if (done) return
      done = true
      onRejected(error)
    }
  }
  
  then (onFulfilled?: ResolveFunction, onRejected?: Nulable<RejectFunction>): TypePromise {
    return new TypePromise((resolve: ResolveFunction, reject: RejectFunction) => {
      const handleResult = (type: ReturnType) => {
        return (result: any) => {
          try {
            const executorFunction = type === ReturnType.ERROR ? reject : resolve
            const checkFunction = type === ReturnType.ERROR ? onRejected : onFulfilled
            return (typeof checkFunction === 'function') ? executorFunction(checkFunction(result)) : executorFunction(result)
          } catch (error) {
            reject(error)
          }
        }
      }
      
      return this.done(handleResult(ReturnType.SUCCESS), handleResult(ReturnType.ERROR))
    })
  }
}
```

Nosso método `then` implementa o que falamos anteriormente, ele recebe dois parâmetros opcionais e retorna uma instancia de uma nova promise. Novamente usamos a técnica do hashmap para podermos selecionar as funções que serão executadas. Caso as funções passadas pelo usuário sejam, de fato, funções, vamos executar estas funções primeiro e depois passar o resultado para o executor final que vai resolver a promise, se não, vamos apenas executar a função de resolução que pode ser `reject` ou `resolve`. Veja que temos um novo método, o `done`.

A função `done(onFulfilled, onRejected)` tem uma semântica mais simples de entender do que a do `then`, embora uma use a outra para que seja possível finalizar a promise. A função `done` segue as seguintes regras:

- Somente um dos dois parâmetros é chamado
- Só pode ser chamada uma vez

Esta função também só pode ser executada no final do `tick` do event loop então temos que ter certeza que ela sempre será agendada para esta execução. Para isso vamos usar a API `process.nextTick` que irá agendar a execução da função na fila de microtasks do Node (veja [este guia](https://github.com/khaosdoctor/my-notes/blob/master/node/node-under-the-hood.md) para entender melhor) e será executado sempre no final do Event Loop:

```ts
import { 
  PromiseStates, 
  ResolveFunction, 
  RejectFunction, 
  ExecutorFunction, 
  Nullable, 
  Thennable, 
  HandlerFunction 
} from './PromiseTypes'

enum ReturnType {
  SUCCESS = 'success',
  ERROR = 'error'
}

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
 
  constructor (executor: ExecutorFunction) {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
    this.executeHandler = this.executeHandler.bind(this)
    this.doResolve(executor, this.resolve, this.reject)
  }
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
		this.executeAllHandlers()
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
    this.executeAllHandlers()
  }
  
  private executeAllHandlers () {
    this.thenHandlers.forEach(this.executeHandler)
    this.thenHandlers = []  
  }
  
  private executeHandler (handler: HandlerFunction) {
    if (this.state === PromiseStates.PENDING) return this.thenHandlers.push(handler)
    if (this.state === PromiseStates.FULFILLED && typeof handler.onFulfilled === 'function') return handler.onFulfilled(this.value)
    if (this.state === PromiseStates.REJECTED && typeof handler.onRejected === 'function') return handler.onRejected(this.value)
  }
  
  private resolve (result: any) {
  	try {
  		const then = this.getThen(result)
  		if (then) return this.doResolve(then.bind(result), this.resolve, this.reject)
  		this.fulfill(result)
  	} catch (error) {
  		this.reject(error)
  	}
  }
  
  private getThen (value: Thennable) {
    const valueType = typeof value
    if (value && (valueType === 'object' || valueType === 'function')) {
      const then = value.then
      if (typeof then === 'function') return then
    }
    return null
  }
  
  private getHandlerType (type: ReturnType, done: boolean, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    return (value: any) => {
      if (done) return
      done = true
      return { error: onRejected, success: onFulfilled }[type](value)
    }
  }
  
  private doResolve (resolverFn: ExecutorFunction, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    let done = false
    try {
			resolverFn(this.getHandlerType(ReturnType.SUCCESS, done, onFulfilled, onRejected), this.getHandlerType(ReturnType.ERROR, done, onFulfilled, onRejected))
    } catch (error) {
      if (done) return
      done = true
      onRejected(error)
    }
  }
  
  then (onFulfilled?: ResolveFunction, onRejected?: Nulable<RejectFunction>): TypePromise {
    return new TypePromise((resolve: ResolveFunction, reject: RejectFunction) => {
      const handleResult = (type: ReturnType) => {
        return (result: any) => {
          try {
            const executorFunction = type === ReturnType.ERROR ? reject : resolve
            const checkFunction = type === ReturnType.ERROR ? onRejected : onFulfilled
            return (typeof checkFunction === 'function') ? executorFunction(checkFunction(result)) : executorFunction(result)
          } catch (error) {
            reject(error)
          }
        }
      }
      
      return this.done(handleResult(ReturnType.SUCCESS), handleResult(ReturnType.ERROR))
    })
  }
  
  private done (onFulfilled?: ResolveFunction, onRejected?: Nullable<RejectFunction>) {
    process.nextTick(() => {
      this.executeHandler({
        onFulfilled,
        onRejected
      })
    })
  }
}
```

Este método nada mais faz do que executar o nosso handler `then` no final do tick.

### Catch

Para finalizar, vamos implementar o método `catch` para que o usuário possa capturar os erros da promise. Ele é bastante simples e também tira proveito da função `done`, diferentemente do `then`, o `catch` **sempre** terá um argumento cujo tipo é uma função de rejeição:

```ts
import { 
  PromiseStates, 
  ResolveFunction, 
  RejectFunction, 
  ExecutorFunction, 
  Nullable, 
  Thennable, 
  HandlerFunction 
} from './PromiseTypes'

enum ReturnType {
  SUCCESS = 'success',
  ERROR = 'error'
}

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
 
  constructor (executor: ExecutorFunction) {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
    this.executeHandler = this.executeHandler.bind(this)
    this.doResolve(executor, this.resolve, this.reject)
  }
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
		this.executeAllHandlers()
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
    this.executeAllHandlers()
  }
  
  private executeAllHandlers () {
    this.thenHandlers.forEach(this.executeHandler)
    this.thenHandlers = []  
  }
  
  private executeHandler (handler: HandlerFunction) {
    if (this.state === PromiseStates.PENDING) return this.thenHandlers.push(handler)
    if (this.state === PromiseStates.FULFILLED && typeof handler.onFulfilled === 'function') return handler.onFulfilled(this.value)
    if (this.state === PromiseStates.REJECTED && typeof handler.onRejected === 'function') return handler.onRejected(this.value)
  }
  
  private resolve (result: any) {
  	try {
  		const then = this.getThen(result)
  		if (then) return this.doResolve(then.bind(result), this.resolve, this.reject)
  		this.fulfill(result)
  	} catch (error) {
  		this.reject(error)
  	}
  }
  
  private getThen (value: Thennable) {
    const valueType = typeof value
    if (value && (valueType === 'object' || valueType === 'function')) {
      const then = value.then
      if (typeof then === 'function') return then
    }
    return null
  }
  
  private getHandlerType (type: ReturnType, done: boolean, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    return (value: any) => {
      if (done) return
      done = true
      return { error: onRejected, success: onFulfilled }[type](value)
    }
  }
  
  private doResolve (resolverFn: ExecutorFunction, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    let done = false
    try {
			resolverFn(this.getHandlerType(ReturnType.SUCCESS, done, onFulfilled, onRejected), this.getHandlerType(ReturnType.ERROR, done, onFulfilled, onRejected))
    } catch (error) {
      if (done) return
      done = true
      onRejected(error)
    }
  }
  
  then (onFulfilled?: ResolveFunction, onRejected?: Nulable<RejectFunction>): TypePromise {
    return new TypePromise((resolve: ResolveFunction, reject: RejectFunction) => {
      const handleResult = (type: ReturnType) => {
        return (result: any) => {
          try {
            const executorFunction = type === ReturnType.ERROR ? reject : resolve
            const checkFunction = type === ReturnType.ERROR ? onRejected : onFulfilled
            return (typeof checkFunction === 'function') ? executorFunction(checkFunction(result)) : executorFunction(result)
          } catch (error) {
            reject(error)
          }
        }
      }
      
      return this.done(handleResult(ReturnType.SUCCESS), handleResult(ReturnType.ERROR))
    })
  }
  
  private done (onFulfilled?: ResolveFunction, onRejected?: Nullable<RejectFunction>) {
    process.nextTick(() => {
      this.executeHandler({
        onFulfilled,
        onRejected
      })
    })
  }
  
  catch (onRejected: RejectFunction) {
    return new TypePromise((resolve: ResolveFunction, reject: RejectFunction) => {
      return this.done(resolve, (error: any) => {
        if(typeof onRejected === 'function') {
          try {
            return resolve(onRejected(error))
          } catch (error) {
            reject(error)
          }
        }
        return reject(error)
      })
    })
  }
}
```

Também retornamos uma nova promise e verificamos se a função passada não é uma outra promise que precisamos resolver antes.

## Bonus: Finally

Há algum tempo a especificação do`promise.prototype.finally` entrou em vigor e já está funcionando no ES6. A função `finally` tem um objetivo simples: Assim como o `finally` que temos em um bloco try/catch, o `finally` de uma promise é **sempre** executado no final de sua resolução, independente se a promise foi resolvida ou rejeitada, porém, diferentemente dos observadores como `then` e `catch`, o finally não retorna outra promise então não é possível encadear chamadas após o finally ser executado.

A implementação é relativamente simples, vamos criar uma propriedade em nossa promise chamada `finalFunction`, que começará como uma função vazia. O objetivo é termos um método `finally` que irá trocar o valor desta propriedade para a função que o usuário passar e então vai executá-la no final dos métodos `fulfill` ou `reject`:

```ts
import { 
  PromiseStates, 
  ResolveFunction, 
  RejectFunction, 
  ExecutorFunction, 
  Nullable, 
  Thennable, 
  HandlerFunction 
} from './PromiseTypes'

enum ReturnType {
  SUCCESS = 'success',
  ERROR = 'error'
}

export class TypePromise {
  private state: PromiseStates = PromiseStates.PENDING
  private finalFunction: Function = () => { }
  private value: any = null
  private thenHandlers: HandlerFunction[] = []
 
  constructor (executor: ExecutorFunction) {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
    this.executeHandler = this.executeHandler.bind(this)
    this.doResolve(executor, this.resolve, this.reject)
  }
  
  private fulfill (value: any) {
    this.state = PromiseStates.FULFILLED
    this.value = value
		this.executeAllHandlers()
    this.finalFunction() // Executamos o finally
  }
  
  private reject (reason: any) {
    this.state = PromiseStates.REJECTED
    this.value = reason
    this.executeAllHandlers()
    this.finalFunction() // Executamos o finally
  }
  
  private executeAllHandlers () {
    this.thenHandlers.forEach(this.executeHandler)
    this.thenHandlers = []  
  }
  
  private executeHandler (handler: HandlerFunction) {
    if (this.state === PromiseStates.PENDING) return this.thenHandlers.push(handler)
    if (this.state === PromiseStates.FULFILLED && typeof handler.onFulfilled === 'function') return handler.onFulfilled(this.value)
    if (this.state === PromiseStates.REJECTED && typeof handler.onRejected === 'function') return handler.onRejected(this.value)
  }
  
  private resolve (result: any) {
  	try {
  		const then = this.getThen(result)
  		if (then) return this.doResolve(then.bind(result), this.resolve, this.reject)
  		this.fulfill(result)
  	} catch (error) {
  		this.reject(error)
  	}
  }
  
  private getThen (value: Thennable) {
    const valueType = typeof value
    if (value && (valueType === 'object' || valueType === 'function')) {
      const then = value.then
      if (typeof then === 'function') return then
    }
    return null
  }
  
  private getHandlerType (type: ReturnType, done: boolean, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    return (value: any) => {
      if (done) return
      done = true
      return { error: onRejected, success: onFulfilled }[type](value)
    }
  }
  
  private doResolve (resolverFn: ExecutorFunction, onFulfilled: ResolveFunction, onRejected: RejectFunction) {
    let done = false
    try {
			resolverFn(this.getHandlerType(ReturnType.SUCCESS, done, onFulfilled, onRejected), this.getHandlerType(ReturnType.ERROR, done, onFulfilled, onRejected))
    } catch (error) {
      if (done) return
      done = true
      onRejected(error)
    }
  }
  
  then (onFulfilled?: ResolveFunction, onRejected?: Nulable<RejectFunction>): TypePromise {
    return new TypePromise((resolve: ResolveFunction, reject: RejectFunction) => {
      const handleResult = (type: ReturnType) => {
        return (result: any) => {
          try {
            const executorFunction = type === ReturnType.ERROR ? reject : resolve
            const checkFunction = type === ReturnType.ERROR ? onRejected : onFulfilled
            return (typeof checkFunction === 'function') ? executorFunction(checkFunction(result)) : executorFunction(result)
          } catch (error) {
            reject(error)
          }
        }
      }
      
      return this.done(handleResult(ReturnType.SUCCESS), handleResult(ReturnType.ERROR))
    })
  }
  
  private done (onFulfilled?: ResolveFunction, onRejected?: Nullable<RejectFunction>) {
    process.nextTick(() => {
      this.executeHandler({
        onFulfilled,
        onRejected
      })
    })
  }
  
  catch (onRejected: RejectFunction) {
    return new TypePromise((resolve: ResolveFunction, reject: RejectFunction) => {
      return this.done(resolve, (error: any) => {
        if(typeof onRejected === 'function') {
          try {
            return resolve(onRejected(error))
          } catch (error) {
            reject(error)
          }
        }
        return reject(error)
      })
    })
  }
  
  finally (finalFunction: Function) {
    if (typeof finalFunction === 'function') this.finalFunction = finalFunction
  }
}
```

## Conclusão

Criamos nossa própria implementação de uma função em Typescript. Com isso temos a vantagem de ter tipagens em tempo de desenvolvimento, porém ainda precisamos checar os tipos durante a execução da função porque, afinal, todo o código é transpilado para JavaScript quando fizermos o build do mesmo. Podemos usar nossa promise desta forma:

```ts
import { TypePromise } from './TypePromise'

function foo (param: any) {
  return new TypePromise((resolve, reject) => {
    if (Math.random() > 0.5) return setTimeout(resolve, 1000, param)
    return setTimeout(reject, 1000, 'error')
  })
}

(() => {
  foo(5)
    .then((value) => console.log(value))
    .catch((error) => console.error(error))
    .finally(() => console.log('aways return'))
})()
```

Note também que estamos utilizando `any` como tipo em vários casos, isto não é uma boa prática ao se utilizar Typescript porque deixamos a tipagem de lado. Então, como uma lição de casa, um desafio bacana seria implementar os tipos genéricos para que `class TypePromise` passe a ser `class TypePromise<T>`. Vamos abordar essa correção na sequencia deste artigo!

Se você estiver interessado em aprender mais, dê uma olhada nestas referências:

- https://www.promisejs.org/implementing/
- https://levelup.gitconnected.com/understand-javascript-promises-by-building-a-promise-from-scratch-84c0fd855720
- https://github.com/khaosdoctor/PromiseFromScratch

Não deixe de acompanhar mais do meu conteúdo no [meu blog](https://blog.lsantos.dev/?utm_source=devto&utm_medium=article&utm_campaign=promises_scratch) e se [inscreva na newsletter](https://blog.lsantos.dev/signup?utm_source=devto&utm_medium=article&utm_campaign=promises_scratch) para receber notícias semanais!