Асинхронные функции Nodejs выполняются не по порядку

#javascript #node.js #asynchronous #async-await #multipartform-data

Вопрос:

Я пытаюсь отправить POST запрос на свой сервер, для этого POST требуется запрос multipart/form-data , поэтому я преобразую местоположения изображений в Buffers , прежде чем отправлять все это в качестве POST запроса, но из-за async поведения Nodejs я не знаю, как заставить функции работать по порядку. Как я должен изменить/переписать этот код?

 import axios from "axios";
import { readFileSync, readFile as rf, PathOrFileDescriptor } from "fs";
import FormData from "form-data";
import dotenv from "dotenv";

dotenv.config();

// readfile promise
const readFile = (file: PathOrFileDescriptor) => {
  return new Promise((resolve, reject) => {
    rf(file, (err, data) => {
      if (err) reject(err.message);
      resolve(data);
    });
  });
};

console.log("Parsing JSON data 💾");
const jsonData = readFileSync("data/data.json").toString();
const products: any[] = JSON.parse(jsonData).products;
console.log("Products Store in JavaScript Object 🛒");

products.forEach((product: any, index: number) => {
  console.log(
    `Creating Product ${index   1}/${products.length} - ${product.name}`
  );

  let requestData = new FormData();
  for (const [key, value] of Object.entries<string>(product)) {
    if (key.includes("image")) {
      console.log(`Converting ${key} in Image Blob 🖼`);
      readFile(value)
        .then((data) => {
          requestData.append(key, data);
          console.log(`${key} Converted ✔`);
        })
        .catch((err) => {
          console.log(`${key} Failed to Convert ❌ - Error=${err}`);
        });
    } else {
      if (key === "variants") {
        requestData.append(key, JSON.stringify(value));
      } else {
        requestData.append(key, value);
      }
    }
  }
  axios
    .post("http://localhost:8000/api/v1/product/create", requestData, {
      headers: {
        "Content-Type": "multipart/form-data",
        Cookie: `access_token=${process.env.ACCESS_TOKEN}; csrftoken=${process.env.CSRF_TOKEN}`,
      },
      withCredentials: true,
    })
    .then(() => {
      console.log(`Product - ${product.name} Created ✔`);
    })
    .catch((err) => {
      console.log(`Product - ${product.name} Failed to create ❌`);
      if (err.response) {
        console.log(err.response.data);
      } else {
        console.log("Internal server error");
      }
    });
});

 

Данные, которые я анализирую, представляют собой массив объектов, подобных этому

 {
      "store": "maki-2",
      "name": "Nike Sportwear Down-fill Windrunner Jacket",
      "description": "The Nike Sportswear Jacket warms up your winter wardrobe with a serious supply of down. This lightweight zip-up style features water-resistant and windproof Nike Shield technology to help keep you comfortable in rough weather. A subtle chevron design, which references the OG Windrunner, graces the chest.",
      "brand": "Nike",
      "gender": "U",
      "category": "outwear",
      "image_v0_1": "data/images/outwear/Nike Sportswear Down-Fill Windrunner/sportswear-down-fill-windrunner-jacket-hHNjxL (3).png",
      "image_v0_2": "data/images/outwear/Nike Sportswear Down-Fill Windrunner/sportswear-down-fill-windrunner-jacket-hHNjxL.jpg",
      "image_v0_3": "data/images/outwear/Nike Sportswear Down-Fill Windrunner/sportswear-down-fill-windrunner-jacket-hHNjxL.png",
      "image_v1_1": "data/images/outwear/Nike Sportswear Down-Fill Windrunner/sportswear-down-fill-windrunner-jacket-hHNjxL (1).jpg",
      "image_v1_2": "data/images/outwear/Nike Sportswear Down-Fill Windrunner/sportswear-down-fill-windrunner-jacket-hHNjxL (1).png",
      "image_v1_3": "data/images/outwear/Nike Sportswear Down-Fill Windrunner/sportswear-down-fill-windrunner-jacket-hHNjxL (2).png",
      "variants": [
        {
          "is_default": false,
          "price": 23000,
          "quantity": 23,
          "size": "M",
          "color": "multi-colored"
        },
        {
          "is_default": true,
          "price": 25000,
          "quantity": 23,
          "size": "L",
          "color": "black"
        }
      ]
    },
 

Комментарии:

1. Если вы хотите читать файл синхронно, вы можете использовать это: nodejs.org/api/fs.html#fs_fs_readfilesync_path_options

Ответ №1:

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

 import axios from "axios";
import {
  readFileSync,
  readFile as rf,
  PathOrFileDescriptor
} from "fs";
import FormData from "form-data";
import dotenv from "dotenv";

dotenv.config();

// readfile promise
const readFile = (file: PathOrFileDescriptor) => {
  return new Promise((resolve, reject) => {
    rf(file, (err, data) => {
      if (err) reject(err.message);
      resolve(data);
    });
  });
};

console.log("Parsing JSON data 💾");
const jsonData = readFileSync("data/data.json").toString();
const products: any[] = JSON.parse(jsonData).products;
console.log("Products Store in JavaScript Object 🛒");


await Promise.all(
  products.map(async(product: any, index: number) => {
    console.log(
      `Creating Product ${index   1}/${products.length} - ${product.name}`
    );

    let requestData = new FormData()

    Object.keys(product).map(async(key: any) => {
      if (key.includes("image")) {
        try {
          const data = await readFile(product[key])
          requestData.append(key, data)
          console.log(`${key} Converted ✔`);
        } catch (e) {
          console.log(`${key} Failed to Convert ❌ - Error=${e}`);
        }
      } else {
        if (key === "variants") {
          requestData.append(key, JSON.stringify(value));
        } else {
          requestData.append(key, value);
        }
      }

      return key;
    });

    await axios
      .post("http://localhost:8000/api/v1/product/create", requestData, {
        headers: {
          "Content-Type": "multipart/form-data",
          Cookie: `access_token=${process.env.ACCESS_TOKEN}; csrftoken=${process.env.CSRF_TOKEN}`,
        },
        withCredentials: true,
      })
      .then(() => {
        console.log(`Product - ${product.name} Created ✔`);
      })
      .catch((err) => {
        console.log(`Product - ${product.name} Failed to create ❌`);
        if (err.response) {
          console.log(err.response.data);
        } else {
          console.log("Internal server error");
        }
      });
    return product;
  })
); 

Ответ №2:

Вы должны связать обещания в цепочку в цикле for.

 var readFilePromise = Promise.resolve();

for (const [key, value] of Object.entries < string > product) {
  if (key.includes("image")) {
    console.log(`Converting ${key} in Image Blob 🖼`);
    readFilePromise = readFilePromise
      .then(() => readFile(value))
      .then((data) => {
        requestData.append(key, data);
        console.log(`${key} Converted ✔`);
      })
      .catch((err) => {
        console.log(`${key} Failed to Convert ❌ - Error=${err}`);
      });
  } else {
    if (key === "variants") {
      requestData.append(key, JSON.stringify(value));
    } else {
      requestData.append(key, value);
    }
  }
}
 

и ваш POST запрос должен быть сделан только после того, как обещание будет выполнено.

 readFilePromise.then(() => {
  axios.post(...)
})