AJAX的学习笔记

94 阅读5分钟

AJAX

AJAX(Asynchronous Javascript And XML)指的是在web应用程序中异步向服务器发送请求。它的实现方式有两种XMLHttpRequest(简称XHR)和Fetch

XHR和Fetch的比较

image.png

练习例子(图片上传)

请求客户端

前端主要功能实现
  1. 图片预览

给input标签添加onchange事件

const selectFile=document.querySelector('.upload-select input');
selectFile.onchange = (e) => {
  //获取选择的文件
  const file = e.target.files[0];
  // 显示预览图
  //文件读取对象
  const reader = new FileReader();
  //读取文件  并将文件读取为DataURL形式
  reader.readAsDataURL(file); // 将文件数据读取为 DataURL(base64编码)
  //读取完成后
  reader.onload = (e) => {
    // 预览图片
    doms.img.src = e.target.result;
    //将图片上传至服务器中
    upload(file);
  };
 
};

image.png

  1. 图片上传进度监控与上传
function upload(file) {
  setProgress(0);
  // XHR
  const xhr = new XMLHttpRequest();
  //配置了一个POST请求  url为http://localhost:9527/upload/single
  xhr.open('POST', 'http://localhost:9527/upload/single'); // 配置请求
  //实时监听文件上传的进度,并执行相关代码
  xhr.upload.onprogress = (e) => {
    //计算上传的
    const percent = Math.floor((e.loaded / e.total) * 100);
    //前端显示上传监控   //图片太小 一下就上传完成了
    setProgress(percent);
    
  };
  //上传完成后,执行相关代码
   xhr.onload = () => {
     showArea('result');
   };
  //上传图片/文件时通常使用 multipart/form-data格式请求体
  const form = new FormData(); // 生成multipart/form-data格式的请求体
  form.append('avatar', file);
  //发送请求  form为请求体(POST请求的请求体)
  xhr.send(form);
}
服务端主要功能实现
  1. 服务器的搭建
//导入express框架    用于构建Nodejs服务器
const express = require('express');

//导入body-parser中间件  用于解析HTTP的请求体
const bodyParser = require('body-parser');

//导入cors中间件  用于处理跨域请求
const cors = require('cors');

//导入配置文件
const config = require('./config');
const app = express();
// 允许跨域请求  
app.use(cors());

//解析json格式的请求体  use表示启动这个bodyParser.json 中间件  并且请求体的大小限制为50MB
app.use(bodyParser.json({ limit: '50mb' }));


//挂载路由   当请求url为当前路由时,就会进入./upload/single 中,从而处理请求
app.use('/upload/single', require('./upload/single'));

//错误处理
app.use((err, req, res, next) => {
  const { UploadError } = require('./upload/errorTypes');
  if (err instanceof UploadError) {
    res.send({
      errCode: err.code,
      errMsg: err.message,
    });
  }
  console.log(err);
});

app.listen(config.global.port, () => {
  console.log(`server start on port ${config.global.port}`);
});


  1. ./upload/single路由处理
//引入相关配置
const config = require('../config');
//引入multer模块 (处理multipart/form-data类型的表单数据) 
const multer = require('multer');
//创建一个multer实例
/*
dest:文件存储地址
limits:文件大小限制
fileFilter:文件过滤函数(检查是否为符合要求的文件后缀名)
*/
const upload = multer({
  dest: config.global.uploadPath,
  limits: {
    fileSize: config.single.sizeLimit,
  },
  //cb callback 表示回调函数 
  fileFilter(req, file, cb) {
    const path = require('path');
    const ext = path.extname(file.originalname);
    if (config.single.exts.includes(ext)) {
      //文件经过过滤后,需要确定是否接收该文件
      //使用 cb(null,true) 来表示接收
      cb(null, true);
    } else {
      //使用 cb(null,false) 来表示拒绝接收
      const { ExtError } = require('./errorTypes');
      //发出一个错误
      cb(new ExtError());
    }
  },
});
//创建一个路由实例
const express = require('express');
const router = express.Router();
//这个路由实例会处理url('./upload/single/')下的post请求
//接收到请求后,执行相应的回调函数
// req http请求对象   res http响应对象  next 如果当前的回调函数不处理请求时,可以使用next函数将请求对象传递给下一个处理函数
router.post('/', async (req, res, next) => {
  //使用multer模块中的单文件处理函数 处理接收到的文件
  upload.single(config.single.fieldName)(req, res, async (err) => {
    //如果发生错误   判断错类型
    if (err instanceof multer.MulterError) {
      const { SizeLimitError, UnexpectedRequest } = require('./errorTypes');
      if (err.message === 'File too large') {
        err = new SizeLimitError();
      } else {
        err = new UnexpectedRequest();
      }
    }
    if (err) {
      next(err);
      return;
    }
    
    //返回响应数据  
    res.send({
      data: '成功上传',
    });
  });
});
知识点补充

文件对象

在nodejs服务器中,通过req.file获取到从前端传过来的文件对象。(多文件对象 使用req.files)

req.file对象有以下属性

属性名含义
fieldname文件数据对应的name(multipart/form-data格式中的name属性)
originalname上传时 文件的名称
encoding上传的文件编码
mimetype上传的文件的MIME类型
size上传的文件的大小 字节数
destination文件保存的路径(在服务器中) 不包含文件名的路径
filename保存的文件名(没有后缀)
path保存的完整路径 包含文件名

multer模块

multer是一个nodejs中间件,用于处理multipart/form-data类型的表单数据,主要用于上传文件。

multer不会处理非multipart/form-data类型的表单数据

创建multer实例时,有一个options对象。下面是options对象属性具体含义。

属性名含义
dest上传文件的存储位置
storage存储引擎对象它允许你自定义文件的存储逻辑,包括指定存储目录、文件名生成规则、文件过滤等
fileFilter文件过滤函数
limits限制上传文件的大小
preservePath保存包含文件名的完整文件路径

storage

1、磁盘存储引擎对象 diskStorage

const storage=multer.diskStorage({
    destination:function(req,file,cb){
        cb(null,'/upload')
    },
    filename:function(req,file,cb){
        cb(null,file.fileldname+Date.now())
    }
})

const upload=multer({storage:storage})

destination 表示一个设置文件存储目录的函数 这里通过设置条件,从而实现不同类型文件存储进不同目录 (也可以直接设置一个目录地址string '/upload')

filename 表示一个设置文件名称的函数
2、内存存储引擎对象 memoryStorage memoryStorage将上传的文件存储在内存中,而不是写入磁盘。这种存储方式适用于临时存储小文件,上传后立即处理,不需要持久化存储的场景。

const storage=multer.memoryStorage()
const uypload=multer({storage:storage})

single

单文件上传

//配置multer
const upload = multer({
  dest: config.global.uploadPath,
  limits: {
    fileSize: config.single.sizeLimit,
  },
  fileFilter(req, file, cb) {
    const path = require('path');
    //获取文件的扩展名
    const ext = path.extname(file.originalname);
    // 检查文件扩展名是否在允许的扩展名列表中
    if (config.single.exts.includes(ext)) {
      //存在  接收文件
      cb(null, true);
    } else {
      //不存在  拒绝文件 
      // cb(null, false);
      const { ExtError } = require('./errorTypes');
      //发出一个错误  并终止请求  并返回错误信息
      cb(new ExtError());
    }
  },
});
//设置相应路由post请求的回调函数 
router.post('/', (req,res,next)=>{
    //单文件处理(multer会自动给上传的文件附上随机名称)
    upload.single(config.single.fieldName)(req,res,(err)=>{
    //判断错误类型
    if(err instanceof multer.MulterError){
        const {SizeLimitError,UnexpectedRequest} =require('./errorTypes');
        if(err.message==='File too large'){
            err=new SizeLimitError();
        }else{
            err=new UnexpectedRequest();
        }
    }
    if(err){
        //发生错误,传给下一个处理
        next(err);
        return;
    }
    })

})

array

多文件上传

const upload=muter({
    dest:config.global.uploadPath,
    limits:{
        fileSize:config.multer.sizeLimit,
    },
    fileFilter(req,file,cb){
        const path=require('path');
        const ext=path.extname(file.originalname);
        if(config.multi.exts.includes(ext))
        {
            cb(null,true);
        }else{
            const {ExtError }=require('./errorTypes');
            cb(new ExtError());
        }
    }
})

router.post('/',(req,res,next)=>{
    upload.array(config.multi.fieldName,config.multi.countLimit)(req,res,(err)=>{
    if(err instanceof multer.MulterError){
        const {SizeLimitError,CountLimitError} =require('./errorTypes');
        if(err.message==='File too large'){
            err=new SizeLimitError();
        }else{
            //文件数量错误
            err=new CountLmitError();
        }
    }
    if(err){
        next(err);
        return;
    }
    })
})