GraphQL:在文件上传期间解决承诺时出错

Posted

技术标签:

【中文标题】GraphQL:在文件上传期间解决承诺时出错【英文标题】:GraphQL: Error when resolving promises during file upload 【发布时间】:2019-08-08 13:19:54 【问题描述】:

所以我一直在处理 GraphQL 上传问题,在说明我的问题之前,先概述一下我正在使用的技术堆栈:

后端:Mongoose、Express、Apollo、GraphQL

前端:VueJS、Apollo、GraphQL

我正在使用Apollo Upload Client 将上传 文件从客户端发送到服务器端。由于我正在从客户端发送文件类型 scalar Upload 的列表,因此我收到了需要解决的承诺列表。在使用 Promise.all() 时,我收到以下错误(奇怪的是,我以前没有遇到过,我也不知道为什么)。如果我上传多个文件,第一个文件会丢失在某个地方,第二个文件会上传....但这并非总是如此。有时它不会发生。也许我没有正确解决或满足承诺。请注意,我还必须通过 Mongoose 将文件名保存在 MongoDB 中

     BadRequestError: Request disconnected during file upload stream parsing.
     at IncomingMessage.request.once (F:\repos\pushbox\node_modules\graphql-upload\lib\processRequest.js:245:35)
     at Object.onceWrapper (events.js:285:13)
     at IncomingMessage.emit (events.js:197:13)
     at resOnFinish (_http_server.js:583:7)
     at ServerResponse.emit (events.js:202:15)
     at onFinish (_http_outgoing.js:683:10)
     at processTicksAndRejections (internal/process/next_tick.js:74:9)
   message: 'Request disconnected during file upload stream parsing.',
   expose: true,
   statusCode: 499,
   status: 499 

我有一个 html 文件输入标签,它接受多个文件,我使用的突变是:

async uploadFiles() 
  // Check if input tag is empty
  if (this.files.length === 0) 
    this.uploadErrorAlert = true;
    return;
  

  // Mutation
  this.isUploading = true;
  await this.$apollo.mutate(
    mutation: UPLOAD_FILES,
    variables: 
      files: this.files,
      id: this.selectedCard.id,
    ,
  )
    .then(() => 
    // clear files from the input tag
      this.files = '';
      this.$refs.selectedFiles.value = '';
      this.isUploading = false;
    )
    .catch((err) => 
      console.error(err);
    );
,

最后,服务器上的解析器是:

/**
 * Uploads files sent on disk and saves
 * the file names in the DB
 *
 * @param Object attachments - List of files for a card
 *
 * @return Boolean - true if upload is
 * successful
 */
uploadFiles: async (_, attachments,  controllers ) => 
  Promise.all(attachments.files.map(async (file) => 
    const  createReadStream, filename  = await file;
    const stream = createReadStream();

    /**
     * We need unique names for every file being uploaded,
     * so we use the ID generated by MongoDB and concat it
     * to the filename sent by the user.
     *
     * Therefore we instantiate an attachment object to get an ID
     */
    const attachment = await controllers.attachment.add( id: attachments.id, file: '' );
    const newFileName = `$attachment.id_$filename`;
    const path = `$process.env.UPLOAD_DIR/$newFileName`;

    await controllers.attachment.update(
      id: attachment.id,
      file: newFileName,
    );

    console.log(`reached for $path`);
    // Attempting to save file in server
    return new Promise((resolve, reject) => stream
      .pipe(createWriteStream(path))
      .on('finish', () => resolve())
      .on('error', (error) => 
        console.log('dude?');
        if (stream.truncated) 
          // Delete the truncated file
          unlinkSync(path);
        
        reject(error);
      ));
  )).then(() => 
    pubsub.publish(ATTACHMENTS_ADDED,  attachmentsChanged: controllers.attachment.getAll() );
  ).catch((err) => 
    console.log(err);
  );
,

任何帮助将不胜感激!

【问题讨论】:

我认为您将文件发送到后端的方式可能存在问题。通常要上​​传文件并将其发送到后端,您必须使用 formData 对象。您必须遍历每个文件并将其作为属性附加。 const fd = new FormData(); this.files.forEach((file) => fd.append('files', file) 所以我只是尝试过,但我在后端什么也没收到。当我在没有 formData 的情况下发送它时,即只发送 this.files,然后我会收到文件。后端 formData 的控制台: id: '5c8b7b5959fa58d85041ba34', files: [ ] 后端 this.files 的控制台: id: '5c8b7b5959fa58d85041ba34', files: [ Promise [Object] , Promise [Object] , Promise [Object] ] 【参考方案1】:

好吧,我不知道我是怎么错过this issue 的,但是这就是解决方案!问题出在我正在使用的模块的 github 问题论坛上。

所以问题是通过在Promise.all() 函数之前使用await 来解决的。所以现在uploadFiles 解析器中的代码如下所示:

await Promise.all(attachments.files.map(async (file) => 
    const  createReadStream, filename  = await file;
    const stream = createReadStream();

    /**
     * We need unique names for every file being uploaded,
     * so we use the ID generated by MongoDB and concat it
     * to the filename sent by the user.
     *
     * Therefore we instantiate an attachment object to get an ID
     */
    const attachment = await controllers.attachment.add( id: attachments.id, file: '' );
    const newFileName = `$attachment.id_$filename`;
    const path = `$process.env.UPLOAD_DIR/$newFileName`;

    await controllers.attachment.update(
      id: attachment.id,
      file: newFileName,
    );

    console.log(`reached for $path`);
    // Attempting to save file in server
    return new Promise((resolve, reject) => stream
      .pipe(createWriteStream(path))
      .on('finish', () => resolve())
      .on('error', (error) => 
        console.log('dude?');
        if (stream.truncated) 
          // Delete the truncated file
          unlinkSync(path);
        
        reject(error);
      ));
  )).then(() => 
    pubsub.publish(ATTACHMENTS_ADDED,  attachmentsChanged: controllers.attachment.getAll() );
  ).catch((err) => 
    console.log(err);
  );

【讨论】:

以上是关于GraphQL:在文件上传期间解决承诺时出错的主要内容,如果未能解决你的问题,请参考以下文章

上传时提示:将文件复制到ftp服务器出错,请检查是不是有权限将文件放到该服务器上。

使用 graphql 文件编译 Typescript 时出错

在 XAMPP 上上传 .sql 文件时出错

使用 nexus-prisma graphql 处理文件上传

laravel 8 中的文件上传 - 在 null 上调用成员函数 getClientOriginalName() 时出错

如何在 hasura graphql 引擎上上传文件或图像