TypeScript - используйте правильную версию setTimeout (узел против окна)

129

Я работаю над обновлением некоторого старого кода TypeScript для использования последней версии компилятора, и у меня возникли проблемы с вызовом setTimeout. Код ожидает вызова функции браузера, setTimeoutкоторая возвращает число:

setTimeout(handler: (...args: any[]) => void, timeout: number): number;

Однако компилятор решает это вместо реализации узла, который возвращает NodeJS.Timer:

setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer;

Этот код не запускается в узле, но типы узлов втягиваются как зависимость от чего-то еще (не знаю, что именно).

Как я могу указать компилятору выбрать нужную setTimeoutмне версию ?

Вот рассматриваемый код:

let n: number;
n = setTimeout(function () { /* snip */  }, 500);

Это вызывает ошибку компилятора:

TS2322: Тип «Таймер» не может быть назначен типу «Число».

Кевин Тай
источник
У вас есть типы: ["node"] в вашем tsconfig.json? См stackoverflow.com/questions/42940954/...
derkoe
@koe Нет, у меня нет опции types: ["node"] в файле tsconfig. Но типы узлов втягиваются как зависимость npm от чего-то еще.
Кевин
1
Вы также можете явно определить «типы» в tsconfig.json - когда вы опускаете «узел», он не используется при компиляции. например, "типы": ["jQuery"]
derkoe
1
Удивительно, что ответ @koe (используйте опцию "типы") не имеет голосов, являясь единственным истинно правильным ответом.
Егор Непомнящих
@ KevinTighe typesне включает, nodeно по- setTimeoutпрежнему получает тип узла, а не тип браузера. typesпо умолчанию для всех типов в node_modules/@types, как описано в typescriptlang.org/tsconfig#types , но даже если вы делаете указать typesи не включают в себя "node", почему до setTimeoutсих пор получить его тип узла , и как вы можете получить тип браузера? Решение @ Axke - это что-то вроде взлома, в основном говоря, что оно возвращает то, что возвращает. TypeScript может все еще находить неправильный тип, но, по крайней мере, он будет постоянно ошибаться.
Денис Хоу,

Ответы:

101

Другой обходной путь, не влияющий на объявление переменной:

let n: number;
n = <any>setTimeout(function () { /* snip */  }, 500);

Кроме того, должно быть возможно использовать windowобъект явно без any:

let n: number;
n = window.setTimeout(function () { /* snip */  }, 500);
dhilt
источник
26
Я думаю, что другой ( window.setTimeout) должен быть правильным ответом на этот вопрос, поскольку это наиболее четкое решение.
amik
5
Если вы используете anyтип, вы на самом деле не даете ответа TypeScript.
С ..
аналогично numberтип приведет к специфическим для TypeScript ошибкам lint, поскольку setTimeoutфункция требует большего.
С ..
2
window.setTimeoutможет вызвать проблемы с фреймворками модульного тестирования (node.js). Лучшее решение - использовать let n: NodeJS.Timeoutи n = setTimeout.
cchamberlain
128
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });

clearTimeout(timer);

Используя, ReturnType<fn>вы получаете независимость от платформы. Вы не будете вынуждены использовать, anyи window.setTimeoutэто не сломается, если вы запустите код без сервера nodeJS (например, страницу, отображаемую на стороне сервера).


Хорошие новости, он также совместим с Deno!

Akxe
источник
10
Насколько я понимаю, это правильный ответ, и он должен быть принятым, поскольку он обеспечивает правильное определение типа для каждой платформы, поддерживающей setTimeout/ clearTimeoutи не используемой any.
afenster
12
Это решение, если вы пишете библиотеку, работающую как на NodeJS, так и в браузере.
yqlim
1
Тип возврата - NodeJS.Timeoutпри setTimeoutпрямом использовании и numberпри использовании window.setTimeout. Не нужно использовать ReturnType.
cchamberlain
@cchamberlain Он вам понадобится, когда вы запустите setTimeoutфункцию и ожидаете, что ее результат будет сохранен в переменной. Попробуйте сами на игровой площадке TS.
Akxe
Для OP и меня TypeScript setTimeoutошибается (по причинам, которые никто не может объяснить), но, по крайней мере, это решение должно маскировать это немного лучше, чем просто использование any.
Денис Хоу,
15

Я думаю, это зависит от того, где вы будете запускать свой код.

Если вашей целевой средой выполнения является узел JS на стороне сервера, используйте:

let timeout: NodeJS.Timeout;
global.clearTimeout(timeout);

Если вашей целевой средой выполнения является браузер, используйте:

let timeout: number;
window.clearTimeout(timeout);
cwouter
источник
6

Скорее всего, это будет работать со старыми версиями, но с версией TypeScript и версией ^3.5.3Node.js ^10.15.3вы должны иметь возможность импортировать специфичные для Node функции из модуля Timers , то есть:

import { setTimeout } from 'timers';

Это будет возвращать экземпляр таймаута типа , NodeJS.Timeoutкоторый вы можете перейти к clearTimeout:

import { clearTimeout, setTimeout } from 'timers';

const timeout: NodeJS.Timeout = setTimeout(function () { /* snip */  }, 500);

clearTimeout(timeout);
Ник Бернард
источник
1
Точно так же, если вам нужна версия браузера setTimeout, что-то вроде const { setTimeout } = windowустранит эти ошибки.
Джек Steam,
2

Я столкнулся с той же проблемой, и наша команда решила использовать обходной путь - просто использовать «любой» для типа таймера. Например:

let n: any;
n = setTimeout(function () { /* snip */  }, 500);

Он будет работать с обеими реализациями методов setTimeout / setInterval / clearTimeout / clearInterval.

Марк Долбырев
источник
2
Да, это работает. Я также понял, что могу просто указать метод непосредственно для объекта окна: window.setTimeout (...). Не уверен, что это лучший способ, но пока я буду придерживаться его.
Кевин
1
Вы можете правильно импортировать пространство имен NodeJS в машинописный текст, см. Этот ответ .
hlovdal 06
Чтобы действительно ответить на вопрос («как я могу указать компилятору выбрать нужную мне версию»), вы можете вместо этого использовать window.setTimeout (), как @dhilt ответил ниже.
Энсон ВанДорен
window.setTimeoutможет не работать с фреймворками модульного тестирования. Здесь есть тип, который можно использовать .. Его NodeJS.Timeout. Вы можете подумать, что находитесь не в среде узлов, но у меня для вас есть новости: Webpack / TypeScript и т. Д. Выполняют node.js.
cchamberlain
0
  • Если вы хотите реальное решение для машинописного текста о таймерах, мы идем:

    Ошибка в возвращаемом типе «число», это не таймер или что-то еще.

    Это решение для машинописных версий версии ~> 2.7:

export type Tick = null | number | NodeJS.Timer;

Теперь мы все исправили, просто объявим вот так:

 import { Tick } from "../../globals/types";

 export enum TIMER {
    INTERVAL = "INTERVAL",
    TIMEOUT = "TIMEOUT", 
 };

 interface TimerStateI {
   timeInterval: number;
 }

 interface TimerI: TimerStateI {
   type: string;
   autoStart: boolean;
   isStarted () : bool;
 }

     class myTimer extends React.Component<TimerI, TimerStateI > {

          private myTimer: Tick = null;
          private myType: string = TIMER.INTERVAL;
          private started: boll = false;

          constructor(args){
             super(args);
             this.setState({timeInterval: args.timeInterval});

             if (args.autoStart === true){
               this.startTimer();
             }
          }

          private myTick = () => {
            console.log("Tick");
          }    

          private startTimer = () => {

            if (this.myType === TIMER.INTERVAL) {
              this.myTimer = setInterval(this.myTick, this.timeInterval);
              this.started = true;
            } else if (this.myType === TIMER.TIMEOUT) {
              this.myTimer = setTimeout(this.myTick, this.timeInterval);
              this.started = true;
            }

          }

         private isStarted () : bool {
           return this.started;
         }

     ...
     }
Никола Лукич
источник
0

Если вы нацеливаетесь setIntervalна window. Тогда вы также можете написать это как

let timerId: number = setInterval((()=>{
    this.populateGrid(true)
  }) as TimerHandler, 5*1000)
}
सत्यमेव जयते
источник