---
title: 'AsyncTask'
description: Execute uma tarefa no pool de threads do libuv e cancele-a com AbortSignal.
---

# AsyncTask

Precisamos falar sobre `Task` antes de falar sobre `AsyncTask`.

## `Task`

Os módulos de complemento geralmente precisam aproveitar os ajudantes assíncronos do libuv como parte de sua implementação. Isso lhes permite agendar o trabalho para ser executado de forma assíncrona, para que seus métodos possam retornar antecipadamente antes que o trabalho seja concluído. Isso permite evitar o bloqueio da execução geral da aplicação Node.js.

O trait `Task` fornece uma maneira de definir uma tarefa assíncrona que precisa ser executada na thread do libuv. Você pode implementar o método `compute` , que será chamado na thread do libuv.

**lib.rs**

```rust {11-13}
use napi::{Task, Env, Result, JsNumber};

struct AsyncFib {
  input: u32,
}

impl Task for AsyncFib {
  type Output = u32;
  type JsValue = JsNumber;

  fn compute(&mut self) -> Result<Self::Output> {
    Ok(fib(self.input))
  }

  fn resolve(&mut self, env: Env, output: u32) -> Result<Self::JsValue> {
    env.create_uint32(output)
  }
}
```

`fn compute` é executado na thread do libuv, você pode executar algum cálculo pesado aqui, o que não bloqueará a thread JavaScript principal.

Você pode notar que existem dois tipos associados no trait `Task`. O `type Output` e o `type JsValue`. `Output` é o tipo de retorno do método `compute`. `JsValue` é o tipo de retorno do método `resolve`.

::: tip
Precisamos de `type Output` e `type JsValue` separados porque não podemos
chamar a função JavaScript de volta em `fn compute`, pois ela não é executada
na thread principal. Portanto, precisamos de `fn resolve`, que é executado na
thread principal, para criar o `JsValue` a partir de `Output` e `Env` e
chamá-lo de volta em JavaScript.

:::

Você pode usar a API de baixo nível `Env::spawn` para iniciar uma `Task` definida no pool de threads libuv. Veja um exemplo na [Referência](/pt-BR/docs/concepts/reference).

Além de `compute` e `resolve`, você também pode fornecer o método `reject` para fazer alguma limpeza quando a `Task` apresenta erro, como `unref` algum objeto:

**lib.rs**

```rust {28}
struct CountBufferLength {
  data: Ref<JsBufferValue>,
}

impl CountBufferLength {
  pub fn new(data: Ref<JsBufferValue>) -> Self {
    Self { data }
  }
}

impl Task for CountBufferLength {
  type Output = usize;
  type JsValue = JsNumber;

  fn compute(&mut self) -> Result<Self::Output> {
    if self.data.len() == 10 {
      return Err(Error::from_reason("len can't be 10".to_string()));
    }
    Ok((&self.data).len())
  }

  fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
    self.data.unref(env)?;
    let output = u32::try_from(output)
      .map_err(|_| Error::new(Status::InvalidArg, "buffer length exceeds u32"))?;
    env.create_uint32(output)
  }

  fn reject(&mut self, env: Env, err: Error) -> Result<Self::JsValue> {
    self.data.unref(env)?;
    Err(err)
  }
}
```

Você também pode fornecer um método `finally` para fazer algo depois que a `Task` for `resolved` ou `rejected`:

**lib.rs**

```rust {27}
struct CountBufferLength {
  data: Ref<JsBufferValue>,
}

impl CountBufferLength {
  pub fn new(data: Ref<JsBufferValue>) -> Self {
    Self { data }
  }
}

#[napi]
impl Task for CountBufferLength {
  type Output = usize;
  type JsValue = JsNumber;

  fn compute(&mut self) -> Result<Self::Output> {
    if self.data.len() == 10 {
      return Err(Error::from_reason("len can't be 5".to_string()));
    }
    Ok((&self.data).len())
  }

  fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
    let output = u32::try_from(output)
      .map_err(|_| Error::new(Status::InvalidArg, "buffer length exceeds u32"))?;
    env.create_uint32(output)
  }

  fn finally(&mut self, env: Env) -> Result<()> {
    self.data.unref(env)?;
    Ok(())
  }
}
```

::: tip
O `#[napi]` macro acima do `impl Task for AsyncFib` é apenas para a geração do `.d.ts`. Se nenhum `#[napi]` for definido aqui, o tipo TypeScript gerado para a `AsyncTask` retornada será `Promise<unknown>`.

:::

## `AsyncTask`

A `Task` que você define não pode ser retornada diretamente para JavaScript, o mecanismo do JavaScript não sabe como executar e resolver o valor da sua `struct`. `AsyncTask` é um wrapper de `Task` que pode ser retornado ao mecanismo do JavaScript. Ele pode ser criado com `Task` e um opcional [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal).

**lib.rs**

```rust
#[napi]
fn async_fib(input: u32) -> AsyncTask<AsyncFib> {
  AsyncTask::new(AsyncFib { input })
}
```

⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️

**index.d.ts**

```ts
export function asyncFib(input: number): Promise<number>
```

### Criar `AsyncTask` com `AbortSignal`

Em alguns cenários, pode ser desejável interromper a `AsyncTask`, na fila, por exemplo, usando `debounce` em algumas tarefas de cálculo. Você pode fornecer um `AbortSignal` para a `AsyncTask`, para que possa interromper a `AsyncTask` se ainda não tiver sido iniciada.

**lib.rs**

```rust {4}
use napi::bindgen_prelude::AbortSignal;

#[napi]
fn async_fib(input: u32, signal: AbortSignal) -> AsyncTask<AsyncFib> {
  AsyncTask::with_signal(AsyncFib { input }, signal)
}
```

⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️

**index.d.ts**

```ts
export function asyncFib(input: number, signal: AbortSignal): Promise<number>
```

Se você chamar `AbortController.abort` antes que o libuv inicie a `AsyncTask`,
o Node-API pode cancelar o trabalho enfileirado e a Promise rejeita com um erro
cujo `name` é `AbortError`.

**test.mjs**

```js {6}
import { asyncFib } from './index.js'

const controller = new AbortController()

asyncFib(20, controller.signal).catch((e) => {
  console.error(e) // Error: AbortError
})

controller.abort()
```

Você também pode fornecer `Option<AbortSignal>` para `AsyncTask` se não souber se a `AsyncTask` precisa ser abortada:

**lib.rs**

```rust
use napi::bindgen_prelude::AbortSignal;

#[napi]
fn async_fib(input: u32, signal: Option<AbortSignal>) -> AsyncTask<AsyncFib> {
  AsyncTask::with_optional_signal(AsyncFib { input }, signal)
}
```

⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️

**index.d.ts**

```ts
export function asyncFib(
  input: number,
  signal?: AbortSignal | undefined | null,
): Promise<number>
```

::: tip
Se `AsyncTask` já tiver iniciado, o Node-API não pode cancelar seu callback
`compute`; se tiver concluído, o resultado da Promise já está definido. Os
callbacks `AbortSignal::on_abort` registrados em Rust ainda executam quando o
signal JavaScript dispara, mesmo quando é tarde demais para cancelar. O
adaptador atual instala seu handler ao converter o argumento e não consulta
`signal.aborted`, portanto passe um signal que ainda não tenha sido abortado.
Ele também atribui `signal.onabort`, substituindo qualquer handler armazenado
nessa propriedade; use `signal.addEventListener('abort', ...)` para listeners
JavaScript independentes.

:::
