perf: prefer async fs calls (#11918)
Synchronous file system operations such as `readFileSync` block the event loop, whereas the asynchronous equivalents (like await `fs.promises.readFile`) do not. This PR replaces certain synchronous fs calls with their asynchronous counterparts in contexts where async operations are already in use, improving performance by avoiding event loop blocking. Most of the synchronous calls were in our file upload code. Converting them to async should theoretically free up the event loop and allow more, other requests to run in parallel without delay
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
import fs from 'fs'
|
||||
import fs from 'fs/promises'
|
||||
import process from 'node:process'
|
||||
|
||||
import type { PayloadComponent, SanitizedConfig } from '../../config/types.js'
|
||||
@@ -147,7 +147,7 @@ ${mapKeys.join(',\n')}
|
||||
|
||||
if (!force) {
|
||||
// Read current import map and check in the IMPORTS if there are any new imports. If not, don't write the file.
|
||||
const currentImportMap = await fs.promises.readFile(importMapFilePath, 'utf-8')
|
||||
const currentImportMap = await fs.readFile(importMapFilePath, 'utf-8')
|
||||
|
||||
if (currentImportMap?.trim() === importMapOutputFile?.trim()) {
|
||||
if (log) {
|
||||
@@ -161,5 +161,5 @@ ${mapKeys.join(',\n')}
|
||||
console.log('Writing import map to', importMapFilePath)
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(importMapFilePath, importMapOutputFile)
|
||||
await fs.writeFile(importMapFilePath, importMapOutputFile)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AcceptedLanguages } from '@payloadcms/translations'
|
||||
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import fs from 'fs'
|
||||
import fs from 'fs/promises'
|
||||
import { compile } from 'json-schema-to-typescript'
|
||||
|
||||
import type { SanitizedConfig } from '../config/types.js'
|
||||
@@ -58,7 +58,7 @@ export async function generateTypes(
|
||||
|
||||
// Diff the compiled types against the existing types file
|
||||
try {
|
||||
const existingTypes = fs.readFileSync(outputFile, 'utf-8')
|
||||
const existingTypes = await fs.readFile(outputFile, 'utf-8')
|
||||
|
||||
if (compiled === existingTypes) {
|
||||
return
|
||||
@@ -67,7 +67,7 @@ export async function generateTypes(
|
||||
// swallow err
|
||||
}
|
||||
|
||||
fs.writeFileSync(outputFile, compiled)
|
||||
await fs.writeFile(outputFile, compiled)
|
||||
if (shouldLog) {
|
||||
logger.info(`Types written to ${outputFile}`)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fs from 'fs'
|
||||
import fs from 'fs/promises'
|
||||
|
||||
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
|
||||
import type { SanitizedConfig } from '../config/types.js'
|
||||
@@ -34,7 +34,7 @@ export const deleteAssociatedFiles: (args: Args) => Promise<void> = async ({
|
||||
|
||||
try {
|
||||
if (await fileExists(fileToDelete)) {
|
||||
fs.unlinkSync(fileToDelete)
|
||||
await fs.unlink(fileToDelete)
|
||||
}
|
||||
} catch (err) {
|
||||
throw new ErrorDeletingFile(req.t)
|
||||
@@ -50,7 +50,7 @@ export const deleteAssociatedFiles: (args: Args) => Promise<void> = async ({
|
||||
const sizeToDelete = `${staticPath}/${size.filename}`
|
||||
try {
|
||||
if (await fileExists(sizeToDelete)) {
|
||||
fs.unlinkSync(sizeToDelete)
|
||||
await fs.unlink(sizeToDelete)
|
||||
}
|
||||
} catch (err) {
|
||||
throw new ErrorDeletingFile(req.t)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import fs from 'fs'
|
||||
import fs from 'fs/promises'
|
||||
|
||||
const fileExists = async (filename: string): Promise<boolean> => {
|
||||
try {
|
||||
await fs.promises.stat(filename)
|
||||
await fs.stat(filename)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
import type { OutputInfo, Sharp, SharpOptions } from 'sharp'
|
||||
|
||||
import { fileTypeFromBuffer } from 'file-type'
|
||||
import fs from 'fs'
|
||||
import { mkdirSync } from 'node:fs'
|
||||
import fs from 'fs/promises'
|
||||
import sanitize from 'sanitize-filename'
|
||||
|
||||
import type { Collection } from '../collections/config/types.js'
|
||||
@@ -121,7 +120,7 @@ export const generateFileData = async <T>({
|
||||
}
|
||||
|
||||
if (!disableLocalStorage) {
|
||||
mkdirSync(staticPath, { recursive: true })
|
||||
await fs.mkdir(staticPath, { recursive: true })
|
||||
}
|
||||
|
||||
let newData = data
|
||||
@@ -291,7 +290,7 @@ export const generateFileData = async <T>({
|
||||
}
|
||||
|
||||
if (file.tempFilePath) {
|
||||
await fs.promises.writeFile(file.tempFilePath, croppedImage) // write fileBuffer to the temp path
|
||||
await fs.writeFile(file.tempFilePath, croppedImage) // write fileBuffer to the temp path
|
||||
} else {
|
||||
req.file = fileForResize
|
||||
}
|
||||
@@ -304,7 +303,7 @@ export const generateFileData = async <T>({
|
||||
// If using temp files and the image is being resized, write the file to the temp path
|
||||
if (fileBuffer?.data || file.data.length > 0) {
|
||||
if (file.tempFilePath) {
|
||||
await fs.promises.writeFile(file.tempFilePath, fileBuffer?.data || file.data) // write fileBuffer to the temp path
|
||||
await fs.writeFile(file.tempFilePath, fileBuffer?.data || file.data) // write fileBuffer to the temp path
|
||||
} else {
|
||||
// Assign the _possibly modified_ file to the request object
|
||||
req.file = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-strict-ignore
|
||||
import { fileTypeFromFile } from 'file-type'
|
||||
import fs from 'fs'
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
import type { PayloadRequest } from '../types/index.js'
|
||||
@@ -11,9 +11,9 @@ const mimeTypeEstimate = {
|
||||
|
||||
export const getFileByPath = async (filePath: string): Promise<PayloadRequest['file']> => {
|
||||
if (typeof filePath === 'string') {
|
||||
const data = fs.readFileSync(filePath)
|
||||
const data = await fs.readFile(filePath)
|
||||
const mimetype = fileTypeFromFile(filePath)
|
||||
const { size } = fs.statSync(filePath)
|
||||
const { size } = await fs.stat(filePath)
|
||||
|
||||
const name = path.basename(filePath)
|
||||
const ext = path.extname(filePath).slice(1)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-strict-ignore
|
||||
import fs from 'fs'
|
||||
import fs from 'fs/promises'
|
||||
import sizeOfImport from 'image-size'
|
||||
import { promisify } from 'util'
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function getImageSize(file: PayloadRequest['file']): Promise<Probed
|
||||
if (file.mimetype === 'image/tiff') {
|
||||
const dimensions = await temporaryFileTask(
|
||||
async (filepath: string) => {
|
||||
fs.writeFileSync(filepath, file.data)
|
||||
await fs.writeFile(filepath, file.data)
|
||||
return imageSizePromise(filepath)
|
||||
},
|
||||
{ extension: 'tiff' },
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { Sharp, Metadata as SharpMetadata, SharpOptions } from 'sharp'
|
||||
|
||||
import { fileTypeFromBuffer } from 'file-type'
|
||||
import fs from 'fs'
|
||||
import fs from 'fs/promises'
|
||||
import sanitize from 'sanitize-filename'
|
||||
|
||||
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
|
||||
@@ -478,7 +478,7 @@ export async function resizeAndTransformImageSizes({
|
||||
|
||||
if (await fileExists(imagePath)) {
|
||||
try {
|
||||
fs.unlinkSync(imagePath)
|
||||
await fs.unlink(imagePath)
|
||||
} catch {
|
||||
// Ignore unlink errors
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-strict-ignore
|
||||
import fs from 'fs'
|
||||
import fs from 'fs/promises'
|
||||
import { Readable } from 'stream'
|
||||
|
||||
/**
|
||||
@@ -16,7 +16,7 @@ const saveBufferToFile = async (buffer: Buffer, filePath: string): Promise<void>
|
||||
streamData = null
|
||||
}
|
||||
// Setup file system writable stream.
|
||||
return fs.writeFileSync(filePath, buffer)
|
||||
return await fs.writeFile(filePath, buffer)
|
||||
}
|
||||
|
||||
export default saveBufferToFile
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-strict-ignore
|
||||
import { promises as fsPromises } from 'fs'
|
||||
import fs from 'fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
@@ -8,7 +8,7 @@ async function runTask(temporaryPath: string, callback) {
|
||||
try {
|
||||
return await callback(temporaryPath)
|
||||
} finally {
|
||||
await fsPromises.rm(temporaryPath, { force: true, maxRetries: 2, recursive: true })
|
||||
await fs.rm(temporaryPath, { force: true, maxRetries: 2, recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,11 +41,11 @@ async function temporaryFile(options: Options) {
|
||||
|
||||
async function temporaryDirectory({ prefix = '' } = {}) {
|
||||
const directory = await getPath(prefix)
|
||||
await fsPromises.mkdir(directory)
|
||||
await fs.mkdir(directory)
|
||||
return directory
|
||||
}
|
||||
|
||||
async function getPath(prefix = ''): Promise<string> {
|
||||
const temporaryDirectory = await fsPromises.realpath(os.tmpdir())
|
||||
const temporaryDirectory = await fs.realpath(os.tmpdir())
|
||||
return path.join(temporaryDirectory, prefix + uuid())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import fs from 'fs'
|
||||
import { promisify } from 'util'
|
||||
import fs from 'fs/promises'
|
||||
|
||||
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
|
||||
import type { SanitizedConfig } from '../config/types.js'
|
||||
@@ -7,8 +6,6 @@ import type { PayloadRequest } from '../types/index.js'
|
||||
|
||||
import { mapAsync } from '../utilities/mapAsync.js'
|
||||
|
||||
const unlinkFile = promisify(fs.unlink)
|
||||
|
||||
type Args = {
|
||||
collectionConfig: SanitizedCollectionConfig
|
||||
config: SanitizedConfig
|
||||
@@ -28,7 +25,7 @@ export const unlinkTempFiles: (args: Args) => Promise<void> = async ({
|
||||
await mapAsync(fileArray, async ({ file }) => {
|
||||
// Still need this check because this will not be populated if using local API
|
||||
if (file?.tempFilePath) {
|
||||
await unlinkFile(file.tempFilePath)
|
||||
await fs.unlink(file.tempFilePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user