Убедитесь, что forEach с асинхронными вызовами выполняется перед другим? - PullRequest
0 голосов
/ 30 апреля 2018

У меня есть функция с несколькими forEach циклами:

async insertKpbDocument(jsonFile) {
    jsonFile.doc.annotations.forEach((annotation) => {
      annotation.entities.forEach(async (entity) => {
        await this.addVertex(entity);
      });
      annotation.relations.forEach(async (relation) => {
        await this.addRelation(relation);
      });
    });
    return jsonFile;
  }

Мне нужно убедиться, что асинхронный код в цикле forEach, вызывающий функцию this.addVertex, действительно выполнен перед выполнением следующего.

Но когда я регистрирую переменные, кажется, что функция this.addRelation вызывается до того, как первый цикл действительно закончится.

Итак, я попытался добавить await условия перед каждым циклом, например:

await jsonFile.doc.annotations.forEach(async (annotation) => {
      await annotation.entities.forEach(async (entity) => {
        await this.addVertex(entity);
      });
      await annotation.relations.forEach(async (relation) => {
        await this.addRelation(relation);
      });
    });

Но такое же поведение.

Может быть, именно функция журнала имеет задержку? Есть идеи?

Ответы [ 5 ]

0 голосов
/ 01 мая 2018

Как мы уже говорили, await не приостанавливает цикл .forEach() и не заставляет 2-й элемент итерации ожидать обработки первого элемента. Итак, если вы действительно пытаетесь выполнить асинхронное упорядочение элементов, вы не сможете выполнить это с помощью цикла .forEach().

Для такого типа проблем async/await действительно хорошо работает с простым циклом for, потому что они приостанавливают выполнение фактического оператора for, чтобы дать вам последовательность асинхронных операций, которые, как вам кажется, и нужны. Кроме того, он работает даже с вложенными циклами for, поскольку все они находятся в одной области действия:

Чтобы показать вам, насколько это проще, используя for/of и await, это можно сделать так:

async insertKpbDocument(jsonFile) {
    for (let annotation of jsonFile.doc.annotations) {
        for (let entity of annotation.entities) {
            await this.addVertex(entity);
        }
        for (let relation of annotation.relations) {
            await this.addRelation(relation);
        }
    }
    return jsonFile;
}

Вы можете написать синхронный код, который фактически упорядочивает асинхронные операции.


Если вы действительно избегаете петли for, и ваше реальное требование заключается только в том, чтобы все вызовы addVertex() выполнялись раньше, чем любые вызовы addRelation(), тогда вы можете сделать это, используя .map() вместо .forEach() и вы собираете массив обещаний, которые затем используете Promise.all() для ожидания всего массива обещаний:

insertKpbDocument(jsonFile) {
    return Promise.all(jsonFile.doc.annotations.map(async annotation => {
        await Promise.all(annotation.entities.map(entity => this.addVertex(entity)));
        await Promise.all(annotation.relations.map(relation => this.addRelation(relation)));
    })).then(() => jsonFile);
}

Чтобы полностью понять, как это работает, выполняется параллельный запуск всех вызовов addVertex() для одной аннотации, ожидание их завершения, затем параллельный запуск всех вызовов addRelation() для одной аннотации и ожидание их всех. Конец. Все аннотации выполняются параллельно. Таким образом, это не очень актуальная последовательность, кроме как в аннотации, но вы приняли ответ с такой же последовательностью и сказали, что он работает, поэтому я покажу немного более простую версию для полноты.


Если вам действительно нужно упорядочить каждый отдельный вызов addVertex(), чтобы не вызывать следующий, пока не будет завершен предыдущий, и вы все равно не собираетесь использовать цикл for, тогда вы можете использовать .reduce() шаблон обещаний, помещенный в вспомогательную функцию для ручной последовательности асинхронного доступа к массиву:

// helper function to sequence asynchronous iteration of an array
// fn returns a promise and is passed an array item as an argument
function sequence(array, fn) {
    return array.reduce((p, item) => {
        return p.then(() => {
            return fn(item);
        });
    }, Promise.resolve());
}


insertKpbDocument(jsonFile) {
    return sequence(jsonFile.doc.annotations, async (annotation) => {
        await sequence(annotation.entities, entity => this.addVertex(entity));
        await sequence(annotation.relations, relation => this.addRelation(relation));
    }).then(() => jsonFile);
}

Это полностью упорядочит все. Это сделает заказ такого типа:

addVertex(annotation1)
addRelation(relation1);
addVertex(annotation2)
addRelation(relation2);
....
addVertex(annotationN);
addRelation(relationN);

, где он ожидает завершения каждой операции, прежде чем перейти к следующей.

0 голосов
/ 30 апреля 2018

forEach выполняет обратный вызов для каждого элемента в массиве и ничего не ждет. Использование await в основном является сахаром для написания promise.then() и вложения всего, что следует в обратном вызове then(). Но forEach не возвращает обещание, поэтому await arr.forEach() не имеет смысла. Единственная причина, по которой это не ошибка компиляции, заключается в том, что спецификация async / await говорит, что вы можете await что угодно, и если это не обещание, вы просто получите его значение ... forEach просто даст вам void .

Если вы хотите, чтобы что-то происходило последовательно, вы можете await в цикле for:

for (let i = 0; i < jsonFile.doc.annotations.length; i++) {
  const annotation = jsonFile.doc.annotations[i]; 
  for (let j = 0; j < annotation.entities.length; j++) {
    const entity = annotation.entities[j];
    await this.addVertex(entity);
  });
  // code here executes after all vertix have been added in order

Редактировать: При наборе этой пары произошли другие ответы и комментарии ... Вы не хотите использовать цикл for, вы можете использовать Promise.all, но все еще может быть некоторая путаница, поэтому я оставлю приведенное выше объяснение на случай, если оно поможет.

0 голосов
/ 30 апреля 2018

foreach вернет void, поэтому его ожидание не принесет особых результатов. Вы можете использовать map, чтобы вернуть все обещания, которые вы создаете сейчас в forEach, и использовать Promise.all, чтобы ожидать все:

async insertKpbDocument(jsonFile: { doc: { annotations: Array<{ entities: Array<{}>, relations: Array<{}> }> } }) {
    await Promise.all(jsonFile.doc.annotations.map(async(annotation) => {
        await Promise.all(annotation.entities.map(async (entity) => {
            await this.addVertex(entity);
        }));
        await Promise.all(annotation.relations.map(async (relation) => {
            await this.addRelation(relation);
        }));
    }));
    return jsonFile;
}
0 голосов
/ 30 апреля 2018

async/await не в пределах forEach.

Простое решение: замените .forEach() на for(.. of ..) вместо.

Подробности в этом аналогичном вопросе .

Если включено no-iterator правило linting, вы получите предупреждение / ошибку linting при использовании for(.. of ..). Есть много дискуссий / мнений по этой теме.

ИМХО, это сценарий, в котором мы можем подавить предупреждение с помощью eslint-disable-next-line или для метода / класса.

* * 1 022 Пример: * 1 023 *
const insertKpbDocument = async (jsonFile) => {
  // eslint-disable-next-line no-iterator
  for (let entity of annotation.entities) {
    await this.addVertex(entity)
  }
  // eslint-disable-next-line no-iterator
  for (let relation of annotation.relations) {
    await this.addRelation(relation)
  }
  return jsonFile
}

Код очень для чтения и работает как положено. Чтобы получить схожую функциональность с .forEach(), нам нужна некоторая акробатика обещаний / наблюдаемых, которую я считаю пустой тратой усилий.

0 голосов
/ 30 апреля 2018

Я понимаю, что вы можете запускать все addVertex одновременно. Объединяя уменьшение с картой, разделенной на два разных набора обещаний, вы можете сделать это. Моя идея:

const first = jsonFile.doc.annotations.reduce((acc, annotation) => {
  acc = acc.concat(annotation.entities.map(this.addVertex));

  return acc;
}, []);

await Promise.all(first);

const second = jsonFile.doc.annotations.reduce((acc, annotation) => {
  acc = acc.concat(annotation.relations.map(this.addRelation));

  return acc;
}, []);

await Promise.all(second);

У вас есть больше петель, но он делает то, что вам нужно, я думаю

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...