Дождитесь выполнения обещаний перед отправкой массива ошибок объектов в CSV - PullRequest
1 голос
/ 19 июня 2020

Я пытаюсь импортировать и удалять большое количество пользователей из Okta, оставаясь в рамках ограничений по скорости и регистрируя любые ошибки, чтобы преуспеть. Приведенный ниже код, похоже, работает, но проблема заключается только в том, что последняя из 5 ошибок, которые я вижу в журнале консоли, не отображается в выведенном CSV.

Я пробовал ряд альтернатив, включая установку вызов csvWriter в .then, а не в .finally. Проблема в том, что он не ждет, пока последняя ошибка будет помещена в массив.

"use strict";
const okta = require("@okta/okta-sdk-nodejs");
const csv = require("csv-parser");
const fs = require("fs");
const createCsvWriter = require("csv-writer").createObjectCsvWriter;

let timeRun = new Date()
  .toISOString()
  .replace(/T/, " ") // replace T with a space
  .replace(/\..+/, "") // delete the dot and everything after
  .replace(/:/g, "."); // replace T with a space

const csvWriter = createCsvWriter({
  path: "errorLog-" + timeRun + ".csv",
  header: [
    { id: "error", title: "Error" },
    { id: "row", title: "Row" },
  ],
});
let record = [];
let currentError = {}

// Enter tenant info and API key here
const client = new okta.Client({
  orgUrl: "https://xxxxxxxx.oktapreview.com",
  token: "xxxxxxxxxxxxxxxxxxxxxxx",
});

let usersToDelete = [];
let currentUserID;

var getUsersToDelete = new Promise((resolve, reject) => {
  fs.createReadStream("testImport.csv")
    .pipe(csv())
    .on("data", (row) => {
      usersToDelete.push(row);
    })
    .on("end", (row) => {
      resolve();
    });
});

getUsersToDelete
  .then(async () => {
    let iCount = 1;
    while (usersToDelete.length > 0) {
        
      var deleteUserTimeout = new Promise((resolve, reject) => {
        setTimeout(async function () {
          currentUserID = usersToDelete.pop();
          client
            .getUser(currentUserID.email)
            .then(async (user) => {
              return user
                .deactivate()
                .then(() => console.log("User has been deactivated"))
                .then(() => user.delete())
                .then(() => console.log("User has been deleted"));
            })
            .catch(async function (error, row) {
                    currentError = { error: error.message, row: "row" };
                    console.error(currentError);
              return error;
            })
            .finally(() => {
              record.push(currentError);
              reject()
            });
          resolve();
        }, 2000);
      });
      await deleteUserTimeout;
      console.log("Timeout" + currentUserID, "Iteration: " + iCount);
      iCount++
    }
  }).finally(async () => {
    await csvWriter.writeRecords(record);
  });

Ответы [ 2 ]

0 голосов
/ 01 июля 2020

Ваше deleteUserTimeout обещание не ждет завершения операции удаления пользователя, и сценарий завершается почти сразу после запуска setTimeout обратного вызова для последнего удаляемого пользователя. CSV будет записан со всеми ошибками, накопленными в record к этому времени, не дожидаясь завершения некоторых операций, включая 5 итераций, которые обнаруживают ошибки.

Basi c Идея такая же, как у @ teppi c, но вы можете записывать каждую обнаруженную ошибку, вместо того, чтобы откладывать их до конца, чтобы уменьшить вероятность потери информации об ошибке в случае сбоя вашего скрипта по каким-либо причинам (например, отключение питания машины) .

const sleep = time => new Promise(resolve => setTimeout(resolve, time))

const deleteUser = async email => {
  const user = await client.getUser(email)
  await user.deactivate()
  await user.delete()
}

const deleteUsers = async users => {
  for (const user of users) {
    try {
      await deleteUser(user.email)
      console.log(`Deleted ${user.email}`)
      await sleep(2000)
    } catch (e) {
      console.error(e.message)
      await csvWriter.writeRecords([{error: e.message, row: 'row'}])
    }
  }
}

getUsersToDelete.then(() => deleteUsers(usersToDelete))
0 голосов
/ 22 июня 2020

В вашем обещании deleteUserTimeout есть гонка. Он разрешается сразу после client.getUser, не дожидаясь результата.

var deleteUserTimeout = new Promise((resolve, reject) => {
  setTimeout(async function () {
    currentUserID = usersToDelete.pop();
    client.getUser(currentUserID.email) // forgot to await this
    //... snip
    resolve(); // resolves immediately
  }, 2000);
});
await deleteUserTimeout; // this won't wait for the delete

Я бы реорганизовал его для ясности (не тестировалось):

async function deleteUser(email) {
  let user = await client.getUser(email);
  await user.deactivate();
  await user.delete();
}

function sleep(timeoutMs) {
  return new Promise(_ => setTimeout(_, timeoutMs));
}

async function deleteUsers(usersToDelete) {
  const errors = [];

  for (let row = 0; row < usersToDelete.length; row++) {
    let currentUserID = usersToDelete[row];
    await sleep(2000);
    await deleteUser(currentUserID.email)
      .catch(error => {
        errors.push({error: error.message, row});
      });
  }

  return errors;
}
...