Включая асинхронные действия, promise.then () и рекурсивный setTimeout, избегая «отложенного антипаттерна» - PullRequest
1 голос
/ 28 октября 2019

Я читал о методах реализации функции опроса и нашел отличную статью о https://davidwalsh.name/javascript-polling. Теперь использование setTimeout вместо setInterval для опроса имеет смысл, особенно с API, которого у меня нетконтроль и показал, чтобы иметь разное время отклика.

Поэтому я попытался реализовать такое решение в своем собственном коде, чтобы бросить вызов моему пониманию обратных вызовов, обещаний и цикла обработки событий. Я следовал указаниям, изложенным в посте, чтобы избежать каких-либо анти-шаблонов Является ли это «Отложенным антипаттерном»? и чтобы обеспечить разрешение обещания до разрешения .then () до разрешения внутреннего обещания и вот где я застреваю. Я собрал некоторый код для имитации сценария, чтобы я мог выделить проблемы.

Мой гипотетический сценарий таков: у меня есть вызов API к серверу, который отвечает userID. Затем я использую этот идентификатор пользователя, чтобы сделать запрос на другой сервер базы данных, который возвращает набор данных, который выполняет некоторую обработку машинного обучения, которая может занять несколько минут.

Из-за задержки задача помещается в очередь задач и после ее завершения обновляет запись базы данных NoSql с isComplete: false до isComplete: true. Это означает, что нам необходимо опрашивать базу данных каждые n секунд, пока мы не получим ответ, указывающий isComplete: true, и затем мы прекратим опрос. Я понимаю, что существует ряд решений для опроса API, но я еще не видел одно, включающее обещания, условный опрос, а не следование некоторым анти-шаблонам, упомянутым в ранее связанном посте. Если я что-то пропустил, и это повтор, я заранее извиняюсь.

Пока процесс описан в следующем коде:

let state = false;
const userIdApi = ()  => {
    return new Promise((res, rej) => {
  console.log("userIdApi");
  const userId = "uid123";
      setTimeout(()=> res(userId), 2000)
    })
}

const startProcessingTaskApi = userIdApi().then(result => {
    return new Promise((res, rej) => {
    console.log("startProcessingTaskApi");
        const msg = "Task submitted";
      setTimeout(()=> res(msg), 2000)
    })
})

const pollDatabase = (userId) => {
    return new Promise((res, rej) => {
  console.log("Polling databse with " + userId)
      setTimeout(()=> res(true), 2000)
    })
}


Promise.all([userIdApi(), startProcessingTaskApi])
    .then(([resultsuserIdApi, resultsStartProcessingTaskApi]) => {
      const id = setTimeout(function poll(resultsuserIdApi){
        console.log(resultsuserIdApi)
        return pollDatabase(resultsuserIdApi)
        .then(res=> {
            state = res
            if (state === true){
              clearTimeout(id);
              return;
              }
            setTimeout(poll, 2000, resultsuserIdApi);
            })
            },2000)
        })

У меня есть вопрос, который связан с этимкод, поскольку он не в состоянии выполнить опрос, как мне нужно:

Я видел в принятом ответе поста Как получить доступ к предыдущим результатам обещания в цепочке .then ()? что нужно «разорвать цепь», чтобы избежать огромных цепочек операторов .then (). Я следовал указаниям, и это, похоже, помогло (перед добавлением опроса), однако, когда я ухожу из системы в каждую строку, кажется, что userIdApi выполняется дважды;один раз там, где он используется в определении startProcessingTaskApi, а затем в строке Promise.all.

Это известное явление? Имеет смысл, почему это происходит. Мне просто интересно, почему это нормально - отправлять два запроса на выполнение одного и того же обещания, или если есть способ предотвратить появление первого запроса и ограничить выполнение функции оператором Promise.all?

Я довольно новичок в Javascript, пришедшем из Python, поэтому любые указатели на то, где мне, возможно, не хватает некоторых знаний, чтобы можно было выполнить эту, казалось бы, простую задачу, были бы очень благодарны.

Ответы [ 2 ]

1 голос
/ 28 октября 2019

Я думаю, что вы почти у цели, кажется, вы просто боретесь с асинхронной природой javascript. Использование обещаний - это определенно способ пойти сюда, а понимание того, как их объединить, является ключом к реализации вашего варианта использования.

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

function delay(millis) {
    return new Promise((resolve) => setTimeout(resolve, millis));
}

Затем вы можете заново реализовать методы "API", используя функцию delay.

const userIdApi = () => {
    return delay(2000).then(() => "uid123");
};

// Make userId an argument to this method (like pollDatabase) so we don't need to get it twice.
const startProcessingTaskApi = (userId) => {
    return delay(2000).then(() => "Task submitted");
};

const pollDatabase = (userId) => {
    return delay(2000).then(() => true);
};

Вы можете продолжить опрос базы данных, просто связав другое обещание в цепочке, когда вашусловие не выполнено.

function pollUntilComplete(userId) {
    return pollDatabase(userId).then((result) => {
        if (!result) {
            // Result is not ready yet, chain another database polling promise.
            return pollUntilComplete(userId);
        }
    });
}

Тогда вы можете собрать все воедино для реализации вашего варианта использования.

userIdApi().then((userId) => {
    // Add task processing to the promise chain.
    return startProcessingTaskApi(userId).then(() => {
        // Add database polling to the promise chain.
        return pollUntilComplete(userId);
    });
}).then(() => {
    // Everything is done at this point.
    console.log('done');
}).catch((err) => {
    // An error occurred at some point in the promise chain.
    console.error(err);
});
0 голосов
/ 28 октября 2019

Это станет лотом проще, если вы действительно сможете использовать ключевые слова async и await.

Используя ту же функцию delay, что и в ответе Джейка:

async function doItAll(userID) {
    await startTaskProcessingApi(userID);
    while (true) {
        if (await pollDatabase(userID)) break;
    }
}
...