← TypeScript: tipos, contratos e diagnóstico
03 / 10 · 55 MIN

Funções e contratos de chamada

Lê assinaturas de funções como promessas sobre argumentos, recetor e conclusão; identifica contratos que escondem dados ou trabalho pendente.

O contrato vem dos dois lados da chamada

Um tipo de função associa parâmetros a um resultado. Em const format: (value: number) => string, o consumidor pode fornecer um número e usar o resultado como texto. Uma arrow atribuída a esse contexto pode receber o tipo do parâmetro sem repetir uma anotação na própria arrow. Isto é inferência contextual, não ausência de verificação.

Ao rever uma assinatura, lê-a nos dois sentidos: que argumentos o chamador tem de fornecer e que resultado pode usar? Um valor predefinido, uma sobrecarga ou um retorno ignorado modifica essa relação. A verificação precisa de corresponder ao modo como a função é realmente invocada, incluindo as diferenças entre chamada síncrona e trabalho assíncrono.

Omissão e valor predefinido

Um parâmetro page?: number pode receber undefined porque o chamador o pode omitir. O corpo tem de tratar essa possibilidade antes de usar métodos numéricos. O chamador também pode passar undefined explicitamente. Isto é diferente de um parâmetro obrigatório cujo tipo não admite ausência.

Um inicializador como count = 3 fornece um valor quando o argumento é undefined ou está ausente. Não substitui zero apenas porque zero é falsy. Quando a aplicação distingue zero tentativas de configuração ausente, a política de valor predefinido deve conservar essa diferença. A regra de propriedades opcionais exatas é tratada separadamente: não se deve aplicá-la automaticamente aos parâmetros de funções.

Callbacks podem ignorar argumentos fornecidos

Se uma API fornece sempre valor e índice, ambos podem ser obrigatórios na assinatura do callback. Um consumidor que só utiliza o valor pode fornecer uma função com menos parâmetros e ignorar o índice. Não é necessário prometer que o índice poderá faltar só para permitir esse consumidor.

Escrever index?: number diz algo mais forte sobre a implementação: permite chamá-la sem índice. Um callback que depende dele passa então a precisar de tratar undefined. A correção deve começar pela API: ela fornece sempre o argumento ou não? Depois, alinha os consumidores com essa promessa. Acrescentar ! em cada callback distribui uma suposição que devia estar explícita no contrato comum.

void não é um protocolo de conclusão

Num tipo contextual () => void, o chamador não usa um resultado útil. A implementação pode devolver um valor que esse contrato ignora. Isso explica por que uma função que devolve um número pode ser usada em certos callbacks sem retorno observado. É diferente de escrever diretamente uma anotação de retorno void no corpo de uma função e devolver um valor incompatível com ela.

Uma função async também pode deixar uma promise sem observação quando o contrato de callback ignora o retorno. A chamada pode regressar enquanto o trabalho continua. Se a conclusão interessa ao coordenador, deve estar representada, por exemplo com Promise<void>, e a implementação deve efetivamente aguardar essa promise. Só mudar o tipo sem mudar o fluxo não cria a espera.

Sobrecargas definem as chamadas públicas

Uma implementação pode precisar de parâmetros opcionais ou de uma união para suportar vários formatos públicos. Quando há sobrecargas, a assinatura usada para escrever o corpo não se torna automaticamente outra assinatura disponível para o chamador. Se as assinaturas públicas admitem um ou três argumentos, o corpo com dois opcionais não autoriza por si só a chamada com dois argumentos.

Também pode existir um problema de resolução quando cada sobrecarga aceita um membro separado, mas o consumidor tem uma variável união. Se o contrato pretendido aceita indistintamente ambos os membros com o mesmo resultado, uma assinatura pública com parâmetro união pode expressar melhor essa necessidade. Escolhe sobrecargas pela relação que precisam de preservar, não pelo número de variantes que é possível escrever.

Recetor e posições também fazem parte do contrato

O parâmetro especial this declara o tipo do recetor sem acrescentar um argumento posicional em execução. Uma API que usa callback.call(record) fornece record como recetor. Uma função normal pode usar esse this; uma arrow conserva o this lexical do lugar onde foi criada. A anotação não faz bind nem altera as regras de chamada do JavaScript.

Ao expandir um array para uma função com dois argumentos fixos, number[] não demonstra que há exatamente duas posições. Uma tupla conserva essa informação. readonly number[] continua a ter comprimento variável, enquanto uma tupla criada com as const pode preservar os dois valores. O objetivo é representar a forma conhecida, sem transformar uma incerteza real numa assertion para any.

Leitura guiada da sequência assíncrona

O exemplo considera um array denso de strings já validado, sem alterações concorrentes. A função deliver exige uma promise por chamada e aguarda cada uma antes de incrementar completed. Se a segunda chamada rejeitar, a função não começa a terceira e a sua própria promise rejeita. O contador não é devolvido nesse caminho. As chamadas anteriores podem já ter efeitos: este ciclo não implementa rollback nem uma transação distribuída.

Nos casos, identifica primeiro quem fornece os argumentos e quem precisa de observar o resultado. A API do índice promete dados a cada chamada; o coordenador assíncrono precisa de uma evidência de conclusão. A resposta correta conserva essas relações sem resolver o aviso por perda de tipos ou por uma notificação antecipada.

// TypeScript 7.0; strict: true; ES2022 target and library.
// Validated dense input; no concurrent input mutation.
async function deliver(
  messages: readonly string[],
  send: (message: string, index: number) => Promise<void>
): Promise<number> {
  let completed = 0;
  for (const [index, message] of messages.entries()) {
    await send(message, index);
    completed += 1;
  }
  return completed;
}
NA PRÁTICA

Segue a ordem das chamadas sem executar. O consumidor pode ignorar index, mas a API fornece-o sempre. Cada chamada cumpre antes da seguinte. Uma rejeição interrompe o fluxo; as chamadas anteriores já concluídas não são revertidas.

Armadilhas comuns

Tornar parâmetros de callback opcionais só porque um consumidor os ignora; esperar que void aguarde uma promise; usar a assinatura do corpo como uma sobrecarga pública.

Tópicos relacionados: Uniões, refinamento de tipos e modelos de estado · Genéricos e relações entre chaves · Dados externos e fronteiras de erro

Leva esta ideia contigo

Uma assinatura útil descreve o que será fornecido e o que poderá ser observado. A implementação tem de conservar essas promessas.

Criar conta

Referência: TypeScript Handbook: Everyday Types · TypeScript 7.0; explicit strict and host configuration