В JavaScript кусочки кода оборачиваются в функции, когда нужно сохранить именованное представление определённой логики работы участка из всей программы.
В нашем случае нам нужно создать некоторый счётчик, который будет виден функцией setInterval() и функцией обратного вызова. Этот счётчик будет сам являться функцией.
Итого получаем 3 функции.
Решение задачи
Давайте сперва создадим главную функцию, которая будет хранить всю логику работы:
function call10times (){}
Теперь у нас есть контейнер для ограниченного запуска интервального таймера. Мы назвали его call10times(). В этом контейнере будут замкнуты нужные нам переменные и функции.
Теперь создадим переменную step, которая будет хранить шаг текущего вызова интервального таймера. Ещё мы объявляем переменную id, которая будет хранить в себе идентификатор из «Карты Активных Таймеров» браузера в момент первого и единственного вызова «интервала».
function call10times (){ let step = 0; let id = 0; function stepplus(){(step < 10)?console.log(step+=1):clearInterval(id)} return id = setInterval(stepplus, 500) }
Внутри мы объявляем функцию stepplus(), которая будет увеличивать значение шага на единицу, когда его значение меньше 10. И возвращать она будет очистку интервала, когда значение шага станет 10 и более. Её тело мы оформили тернарным оператором (УСЛОВИЕ?ДЕЙСТВИЕ ЕСЛИ ВЕРНО:ДЕЙСТВИЕ ЕСЛИ ЛОЖНО).
В таком виде функция мало полезна т. к. она просто выводит в консоль значения текущего шага вызова интервала. Давайте её сделаем более универсальной.
Пример работы:

Улучшение № 1 — Передача нужной нам функции обратного вызова в качестве параметра в call10times()
Представим, что у нас уже есть какая-то функция с логикой работы, которую мы хотим вызывать в интервале ограниченное количество раз. Давайте её передадим.
function call10t (myfun){ let step = 0; let id = 0; function stepplus(){ if (step < 10) { return step+=1, myfun() } else { return clearInterval(id)} } return id = setInterval(stepplus, 500) }
Пример вызова с передачей стрелочной функции в качестве параметра:
call10t (()=>console.log(1))
Пример работы:

Улучшение № 2 — передача с аргументами
Если мы хотим передавать коллбэк с параметрами, то мы можем учитывать передачу этих параметров двумя способами. Всё что будет добавлено через запятую после слова myfun, будет попадать в объект arguments.
function call10ti (myfun){ let step = 0; let id = 0; let a = arguments; function stepplus(){ if (step < 10) { return step+=1, myfun(...[...a].slice(1)) } else { return clearInterval(id)} } return id = setInterval(stepplus, 500) }
Пример для вызова:
call10ti (console.log, 1) call10ti (console.log, 777, 555)
Пример работы:

Можно воспользоваться встроенной в JavaScript конструкцией через оператор spread — троеточие.
function call10tim (myfun, ...args){ let step = 0; let id = 0; function stepplus(){ if (step < 10) { return step+=1, myfun(...args) } else { return clearInterval(id)} } return id = setInterval(stepplus, 500) }
Пример для вызова:
call10ti (console.log, 22) call10ti (console.log, 44, 999)
Пример работы:

Улучшение № 3 — дополнительная передача требуемого количества вызовов интервального коллбека
Можно воспользоваться встроенной в JavaScript конструкцией через оператор spread — троеточие.
function callanytimes (myfun, times, ...args){ let step = 0; let id = 0; function stepplus(){ if (step < times) { return step+=1, myfun(...args) } else { return clearInterval(id)} } return id = setInterval(stepplus, 100) }
Пример для вызова:
callanytimes (console.log, 3, 'TikTak') callanytimes (console.log, 12, 'TikTak') callanytimes (console.log, 12, 'TikTak', 'BomBom')
Пример работы:

Информационные ссылки
JavaScript | Как остановить setInterval()?
Стандарт ECMAScript — Раздел «14.3.3 Destructuring Binding Patterns» — https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-destructuring-binding-patterns
Стандарт HTML — Раздел «Timers» — https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers