LINUX.ORG.RU

Как положено делать паузу в JavaScript?

 , ,


1

2

Есть вот такой скрипт: https://github.com/ianoxley/open-selected-links/blob/master/open-selected-links.user.js Открывает в новых табах ссылки при их выделении с зажатым Ctrl. Судя по всему, предназначался для страницы поиска Гугла.

Большинство сайтов не любят, когда кто-то открывает десятки страниц одновременно. Поэтому между вызовами GM_openInTab в цикле в функции openSelectedLinks (строка 39) нужно сделать паузу несколько секунд.

Погуглил как делать паузу на джаваскрипте и запутался. Что такое async и await? Когда вызов setTimeout остановит скрипт, а когда — нет?

ОТВЕТ: Для паузы 10 с нужно заменить вызов GM_openInTab(selectedLinks[i].href) на setTimeout(_ => GM_openInTab(selectedLinks[i].href), 10000*(selectedLinks.length-i))

Пока получился такой скрипт:

// ==UserScript==
// @name         open-selected-links
// @namespace    http://tampermonkey.net/
// @version      2026-07-15
// @description  Opens selected links in new tabs
// @author       ianoxley
// @match        *
// @grant        GM_openInTab
// ==/UserScript==

(function() {
    'use strict';

    document.addEventListener('keydown', function(e) {
        const ctrlKeyCode = 17;
        const cmdKeyCode = 224;

        if (e.keyCode == ctrlKeyCode || e.keyCode == cmdKeyCode) {
            document.addEventListener('mouseup', openSelectedLinks, true);
        }
    }, true);

    // Remove the mousedown handler so it doesn't open links just by selecting text *without* ctrl / cmd
    document.addEventListener('keyup', function(e) {
        document.removeEventListener('mouseup', openSelectedLinks, true);
        }, true);

    function openSelectedLinks(e) {
        const selection = window.getSelection();
        if (selection) {
            const selectedLinks = selection.getRangeAt(0).cloneContents().querySelectorAll('a');

            const n = selectedLinks.length;
            for (let i = n-1; i >= 0; --i) {
                setTimeout(_ => GM_openInTab(selectedLinks[i].href), 10000 * (n - i));
            } // timers open new tabs once per 10 000 ms
        }
    }
})();

Нормально работает, если выделять текст с конца — снизу вверх справа налево. Если сверху вниз — иногда открывает ссылки в обратном порядке.

★★★★★

Последнее исправление: question4 (всего исправлений: 4)

async это как вызов select
await это как если ты после вызова select в цикле ждёшь что в каком-то из дескрипторов произойдёт шевеление чтобы считать оттуда данные и выполнить код
Вызов setTimeout скрипт не останавливает, а просто заносит таймер в список таймеров движка яваскрипт, которые движок яваскрипта периодически чекает, останавливая скрипт, и если таймеру пора сработать, то, пока твой скрипт стоит, выполнится обработчик таймера и твой скрипт продолжится. Но это как вселенная моргнула, и никто из жителей вселенной этого не заметил.

Bad_ptr ★★★★★
()
Последнее исправление: Bad_ptr (всего исправлений: 2)
Ответ на: комментарий от Bad_ptr

Вызов setTimeout скрипт не останавливает, а просто заносит таймер в список таймеров движка яваскрипт, которые движок яваскрипта периодически чекает, останавливая скрипт, и если таймеру пора сработать, то, пока твой скрипт стоит, выполнится обработчик таймера и твой скрипт продолжится.

В скрипте по ссылке я заменил GM_openInTab(selectedLinks[i].href) на setTimeout(GM_openInTab(selectedLinks[i].href), (selectedLinks.length - i) * 1000). Я рассчитывал открывать вкладки с интервалом 1 секунда. То есть последнияя в списке откроется через 1000 мс, предпоследняя — через 2000 мс и т.д. Вместо этого все открылись одновременно. Что я сделал неправильно?

question4 ★★★★★
() автор топика
Ответ на: комментарий от anonymous_sama
function sleep (ms) {  
  return new Promise(resolve => setTimeout(resolve, ms))  
}

Просто добавить sleep(1000) после вызова GM_openInTab()? Не помогло, открываются одновременно.

https://chatgpt.com/s/t_6a5745798f54819188c4adf67409f151

«Unable to load site»

question4 ★★★★★
() автор топика
Последнее исправление: question4 (всего исправлений: 1)

Браузеры не любят когда окна открываются не в ответ на действие(клик) пользователя и блочат такие попытки. Причем между действием и открытием должен пройти небольшой отрезок времени. Точное значение не помню, но меньше секунды. Поэтому то что делаешь работать не будет.

А в целом ты хочешь setTimeout

cobold ★★★★★
()
Последнее исправление: cobold (всего исправлений: 1)
Ответ на: комментарий от question4

Ну серьезно это в каком каменном веке, надо жить и как можно дойти до 5ти звезд, если не можешь разблокировать. Ну хорошо вот тебе портянка:

(function () {
  'use strict'

  // Increase this if the browser still opens tabs too quickly.
  const TAB_OPEN_DELAY_MS = 1000

  let modifierPressed = false
  let mouseupAttached = false
  let openingController = null
  let openingInProgress = false

  document.addEventListener('keydown', handleKeyDown, true)
  document.addEventListener('keyup', handleKeyUp, true)
  document.addEventListener('mouseup', handleMouseUp, true)

  window.addEventListener('blur', resetModifier)
  window.addEventListener('pagehide', cancelOpening)

  function handleKeyDown(event) {
    if (event.key === 'Escape') {
      cancelOpening()
      return
    }

    if (!isModifierKey(event)) return

    modifierPressed = true
    mouseupAttached = true
  }

  function handleKeyUp(event) {
    if (!isModifierKey(event)) return

    modifierPressed = false
    mouseupAttached = false
  }

  function handleMouseUp(event) {
    if (!mouseupAttached) return

    if (!modifierPressed && !event.ctrlKey && !event.metaKey) {
      return
    }

    // Prevent the same mouseup from starting another opening sequence.
    mouseupAttached = false

    void openSelectedLinks()
  }

  function isModifierKey(event) {
    return event.key === 'Control' || event.key === 'Meta'
  }

  async function openSelectedLinks() {
    // Do not allow two tab-opening loops to run concurrently.
    if (openingInProgress) return

    const links = getSelectedLinks()

    if (links.length === 0) return

    openingInProgress = true
    openingController = new AbortController()

    const signal = openingController.signal

    try {
      for (const url of links) {
        if (signal.aborted) break

        GM_openInTab(url, {
          active: false,
          insert: true,
          setParent: true
        })

        // Always wait before opening the next tab.
        await delay(TAB_OPEN_DELAY_MS, signal)
      }
    } catch (error) {
      if (error?.name !== 'AbortError') {
        console.error('[Open Selected Links]', error)
      }
    } finally {
      openingController = null
      openingInProgress = false
    }
  }

  function getSelectedLinks() {
    const selection = window.getSelection()

    if (
      !selection ||
      selection.rangeCount === 0 ||
      selection.isCollapsed
    ) {
      return []
    }

    const urls = new Set()

    for (let index = 0; index < selection.rangeCount; index++) {
      const range = selection.getRangeAt(index)
      const fragment = range.cloneContents()

      fragment.querySelectorAll('a[href]').forEach(link => {
        addLink(link, urls)
      })

      addContainingLink(range.startContainer, urls)
      addContainingLink(range.endContainer, urls)
    }

    return Array.from(urls)
  }

  function addContainingLink(node, urls) {
    const element = node.nodeType === Node.ELEMENT_NODE
      ? node
      : node.parentElement

    addLink(element?.closest?.('a[href]'), urls)
  }

  function addLink(link, urls) {
    if (!link || shouldIgnoreLink(link)) return

    try {
      const url = new URL(link.getAttribute('href'), location.href)

      if (url.protocol !== 'http:' && url.protocol !== 'https:') {
        return
      }

      urls.add(url.href)
    } catch (error) {
      console.debug('[Open Selected Links] Invalid URL ignored', error)
    }
  }

  function shouldIgnoreLink(link) {
    if (!isGoogleResultsPage()) return false

    const text = link.textContent.trim().toLowerCase()

    return text === 'cached' || text === 'similar'
  }

  function isGoogleResultsPage() {
    return (
      location.hostname === 'google.com' ||
      location.hostname.startsWith('www.google.') ||
      location.hostname.includes('.google.')
    )
  }

  function delay(milliseconds, signal) {
    return new Promise((resolve, reject) => {
      if (signal.aborted) {
        reject(createAbortError())
        return
      }

      const timeout = window.setTimeout(() => {
        signal.removeEventListener('abort', handleAbort)
        resolve()
      }, milliseconds)

      function handleAbort() {
        window.clearTimeout(timeout)
        reject(createAbortError())
      }

      signal.addEventListener('abort', handleAbort, {
        once: true
      })
    })
  }

  function createAbortError() {
    return new DOMException('Tab opening cancelled', 'AbortError')
  }

  function resetModifier() {
    modifierPressed = false
    mouseupAttached = false
  }

  function cancelOpening() {
    openingController?.abort()
  }
})()
anonymous_sama ★★★★★
()
Ответ на: комментарий от anonymous_sama

Ну серьезно это в каком каменном веке, надо жить и как можно дойти до 5ти звезд, если не можешь разблокировать.

Блокировка по IP выглядит как просьба перезагрузить страницу через несколько минут, отключив VPN?

вот тебе портянка: …

Главный вопрос: как его запустить? :) Выделение с Ctrl не срабатывает. С Alt и Ctrl-Alt — тоже.

question4 ★★★★★
() автор топика
Ответ на: комментарий от Bad_ptr

setTimeout(_ => GM_openInTab(selectedLinks[i-1].href), 1000*i);

Спасибо! Именно это и требовалось.

(Похоже, в прошлый раз я не сохранил скрипт перед проверкой.)

for (let i = 1; i <= n; ++n)

Про ошибку ++i вместо ++n уже написали. А ещё список необходимо открывать в обратном порядке, так как новая вкладка каждый раз открывается непосредственно справа от той, где работает скрипт.

На этом задача решена. Ещё раз спасибо.

question4 ★★★★★
() автор топика
Ответ на: комментарий от cobold

Браузеры не любят когда окна открываются не в ответ на действие(клик) пользователя и блочат такие попытки.

Поэтому в GreaseMonkey такие функции заблокированы по умолчанию и их каждую надо явно включать комадной вида // @grant GM_openInTab

то что делаешь работать не будет.

В итоге заработало.

question4 ★★★★★
() автор топика
Ответ на: комментарий от Noob_Linux

Тут явно ошибка с ++n т.к. n это константа

Спасибо. Хорошо сочетается с аватаркой :)

ну и i надо бы инкрементить что бы он разный был для разных таймаутов…

Да, так и должно происходить. i перебирает номера ссылок и эти же номера определяют длительность пауз. Как i * 10000 или (n-i) * 10000

question4 ★★★★★
() автор топика
Ответ на: комментарий от dataman

готовых расширений «Open Selected Links» чуть меньше, чем грязи.

И большинство открывают всё сразу.

P.S. Firefox.

question4 ★★★★★
() автор топика
Последнее исправление: question4 (всего исправлений: 1)
Ответ на: комментарий от question4

Кстати при использовании var вместо let была бы дополнительная проблема с тем что на момент вызова колбека таймера использовалось бы одно и то же значение i.

Tanger ★★★★★
()
Ответ на: комментарий от Tanger

при использовании var вместо let была бы дополнительная проблема с тем что на момент вызова колбека таймера использовалось бы одно и то же значение i.

Спасибо за совет. Заменил все оставшиеся var на const. Вроде, работает.

question4 ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Никто не пошутил, что он сам делает паузы когда хочет?

Наблюдал, что когда браузер тормозил, начало открываться минуты через полторы. Но порядок открытия соблюдался.

question4 ★★★★★
() автор топика
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария