代码下载
前后端交互模式
接口调用方式:原生ajax、基于jQuery的ajax、fetch、axios。
URL 地址格式
传统形式的 URL 格式:schema://host:port/path?query#fragment
- schema:协议。例如http、https、ftp等
- host:域名或者IP地址
- port:端口, http默认端口80,可以省略
- path:路径, 例如/abc/a/b/c
- query :查询参数,例如 uname=lisi&age=12
- fragment :锚点(哈希Hash),用于定位页面的某个位置
Restful 形式的 URL HTTP请求方式:GET 查询、POST 添加、PUT 修改、DELETE 删除。
Promise
JavaScript的执行环境是单线程,常见的异步调用:定时任务、ajax、事件函数。多次异步调用的结果顺序不确定,异步调用结果如果存在依赖需要嵌套。
// 定时任务
let v = 1;
setTimeout(() => {
v = 2;
}, 1000);
console.log('v: ', v);
// 多次异步调用
$.get('http:localhost/data1', function(data) {
console.log('data: ', data);
});
$.get('http:localhost/data2', function(data) {
console.log('data: ', data);
});
$.get('http:localhost/data3', function(data) {
console.log('data: ', data);
});
// 嵌套调用
$.get('http:localhost/data1', function(data) {
console.log('data: ', data);
$.get('http:localhost/data2', function(data) {
console.log('data: ', data);
$.get('http:localhost/data3', function(data) {
console.log('data: ', data);
});
});
});
Promise 是异步编程的一种解决方案,从语法上讲,Promise是一个对象,从它可以获取异步操作的消息。使用 Promise 主要有以下好处:
- 可以避免多层异步调用嵌套问题(回调地狱)
- Promise 对象提供了简洁的API,使得控制异步操作更加容易
Promise 基本用法
实例化 Promise 对象,构造函数中传递函数,该函数用于处理异步任务,resolve 和 reject 两个参数用于处理成功和失败两种情况,并通过 p.then 获取处理结果:
var p = new Promise(function(resolve, reject){
// 成功时调用 resolve()
// 失败时调用 reject()
});
p.then(funciton(ret){
// 从resolve得到正常结果
}, function(ret){
// 从reject得到错误信息
});
let p = new Promise(function(resolve, reject) {
setTimeout(() => {
let f = true;
if (f) {
resolve('正常');
} else {
reject('出错啦');
}
}, 1000);
});
p.then(function(data) {
console.log('data: ', data);
}, function(info) {
console.log('info: ', info);
});
基于Promise处理Ajax请求
处理原生Ajax:
let p = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open('GET', 'http:localhost/data1');
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject('出错啦');
}
}
};
});
p.then(function(data) {
console.log('data: ', data);
}, function(info) {
console.log('info: ', info);
})
发送多次ajax请求:
function queryData(url) {
let p = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject('出错啦');
}
}
};
});
return p;
}
queryData('http:localhost/data1').then(function(data) {
console.log('data: ', data);
return queryData('http:localhost/data2');
}).then(function(data) {
console.log('data: ', data);
return queryData('http:localhost/data3');
}).then(function(data) {
console.log('data: ', data);
});
then参数中的函数返回值:
-
返回 Promise 实例对象,返回的该实例对象会调用下一个 then
-
返回普通值,返回的普通值会直接传递给下一个 then,通过 then 参数中的第一个函数的参数接收该值
function createPromise(flag) { let q = new Promise(function(resolve, reject) { setTimeout(() => { if (flag) { resolve('成功'); } else { reject('失败'); } }, 1000); }); return q; } createPromise(false).then(function(data) { console.log('data: ', data); return data; }, function(info) { console.log('info: ', info); return info; }).then(function(data) { console.log('--data: ', data, '--'); }, function(info) { console.log('--info: ', info, '--'); }) // 打印结果 // info: 失败 // --data: 失败 --
Promise常用的API
-
p.then() 得到异步任务的正确结果,也可以得到异步任务的正确结果和异常信息。
-
p.catch() 获取异常信息。
-
p.finally() 成功与否都会执行(尚且不是正式标准)。
-
Promise.all() 接受一个数组作参数,数组中的对象均为promise实例(如果不是一个 promise,该项会被用 Promise.resolve 转换为一个promise)。并发处理多个异步任务,所有任务都执行完成才能得到结果(全部任务成功才算成功,只要有一个任务异常就只能得到此任务的异常信息)。
-
Promise.race() 方法同样接受一个数组作参数。并发处理多个异步任务,只要有一个任务完成就能得到结果。
function queryData(flag, index) { let p = new Promise(function(resolve, reject) { setTimeout(() => { if (flag) { resolve('成功-' + index); } else { reject('失败-' + index); } }, 1000); }); return p; } queryData(true, 1).then(function(data){ console.log('then success data: ', data); }).catch(function(info) { console.log('catch info: ', info); }).finally(function() { console.log('finally'); }); // 打印结果: // then success data: 成功-1 // finally queryData(false, 2).then(function(data){ console.log('then success data: ', data); }).catch(function(info) { console.log('catch info: ', info); }).finally(function() { console.log('finally'); }); // 打印结果: // catch info: 失败-2 // finally Promise.all([queryData(true, 3), queryData(false, 4), queryData(false, 5)]).then(function(result) { console.log('result: ', result); }, function(info) { console.log('error: ', info); }); // 打印结果: // error: 失败-4 Promise.race([queryData(true, 6), queryData(false, 7), queryData(true, 8)]).then(function(result) { console.log('result: ', result); }, function(info) { console.log('error: ', info); }); // 打印结果: // result: 成功-6
fetch
-
Fetch API是新的ajax解决方案,更加简单地获取数据,功能更强大、更灵活。
-
语法结构
fetch(url, options).then(fn1).then(fn1).catch(fn2)
,Fetch会返回Promise,所以我们可以使用 then 拿到请求成功的结果。 -
fetch不是ajax的进一步封装,而是原生js,基于Promise实现,没有使用XMLHttpRequest对象。
fetch('http://localhost/data1').then(function(response) { // response 是响应体 console.log('response: ', response); // text()方法属于fetchAPI的一部分,它返回一个Promise实例对象,用于获取后台返回的数据 let r = response.text(); console.log('text: ', r); return r; }).then(function(data) { // 这里得到的才是最终数据 console.log('data: ', data); }); // 打印结果: // response: Response {type: 'cors', url: 'http://localhost/data1', redirected: false, status: 200, ok: true, ...} // text: Promise {[[PromiseState]]: 'pending', [[PromiseResult]]: undefined} // data: data1
fetch 请求参数,常用配置选项:
- method(String): HTTP请求方法,默认为GET (GET、POST、PUT、DELETE)
- body(String): HTTP的请求参数
- headers(Object): HTTP的请求头,默认为{}
fetch 响应数据格式:
-
text():将返回体处理成字符串类型
-
json():返回结果和 JSON.parse(responseText)一样
// get fetch('http://localhost/fdata?id=1').then(function(res) { return res.text(); }).then(function(data) { console.log('data: ', data); }); // get 动态参数 fetch('http://localhost/fdata/2', { method: 'get' }).then(function(res) { return res.text(); }).then(function(data) { console.log('data: ', data); }); // post url-encoded 参数 fetch('http://localhost/fdata', { method: 'post', body: 'uname=张三&password=123456', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function(res) { return res.text(); }).then(function(data) { console.log('data: ', data); }); // post josn 参数 fetch('http://localhost/fdata', { method: 'post', body: JSON.stringify({ uname: '李四', password: '123456' }), headers: { 'Content-Type': 'application/json' } }).then(function(res) { return res.json(); }).then(function(data) { console.log('data: ', data); }); // delete fetch('http://localhost/fdata/3', { method: 'delete' }).then(function(res) { return res.text(); }).then(function(data) { console.log('data: ', data); }); // put fetch('http://localhost/fdata/4', { method: 'put', body: JSON.stringify({ uname: '王五', password: '123456' }), headers: { 'Content-Type': 'application/json' } }).then(function(res) { return res.json(); }).then(function(data) { console.log('data: ', data); });
axios
axios(官网)是一个基于Promise 用于浏览器和 node.js 的 HTTP 客户端。它具有以下特征:
- 支持浏览器和 node.js
- 支持 promise
- 能拦截请求和响应
- 自动转换 JSON 数据
基本用法:
axios.get('/adata')
.then(ret=>{
// data属性名称是固定的,用于获取后台响应的数据
console.log(ret.data)
})
axios 的响应结果的主要属性:
- data : 实际响应回来的数据
- headers :响应头信息
- status :响应状态码
- statusText :响应状态信息
axios 的参数传递:
// get
axios.get('http://localhost/data?id=1')
.then(function(res) {
console.log(res.data);
});
// get 动态参数
axios.get('http://localhost/data/2')
.then(function(res) {
console.log(res.data);
});
// get params 形式参数
axios.get('http://localhost/data', {
params: {
id: 3
}
}).then(function(res) {
console.log(res.data);
});
// post 通过 URLSearchParams 传递url-encoded参数
let params = new URLSearchParams();
params.append('uname', '张三');
params.append('password', '123');
console.log(params);
axios.post('http://localhost/data', params).then(function(res) {
console.log(res.data);
});
// post json参数
axios.post('http://localhost/data', {
uname: '李四',
password: '123'
}).then(function(res) {
console.log(res.data);
});
// delete 与 get 类似
axios.delete('http://localhost/data?id=4')
.then(function(res) {
console.log(res.data);
});
axios.delete('http://localhost/data/5').then(function(res) {
console.log(res.data);
});
axios.delete('http://localhost/data', {
params: {
id: 6
}
}).then(function(res) {
console.log(res.data);
});
// put 与 post 类似
axios.put('http://localhost/data/7', {
uname: '王五',
password: '123'
}).then(function(res) {
console.log(res.data);
});
axios 还可以如此传递参数:
axios({
method: '请求类型',
url: '请求的URL地址',
data: { /* POST数据 */ },
params: { /* GET参数 */ }
}).then(callback)
axios 的全局配置:
-
axios.defaults.timeout = 3000; // 超时时间
-
axios.defaults.baseURL = 'http://localhost:3000'; // 默认地址
-
axios.defaults.headers['mytoken'] = 'aqwerwqwerqwer2ewrwe23eresdf23'; //设置请求头
-
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
-
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; // 配置公共的 post 的 Content-Type
axios.defaults.baseURL = 'http://localhost'; axios.defaults.headers['mytoken'] = 'zhaoliu'; axios.get('data-json') .then(function(res) { console.log(res.data); });
axios 拦截器
-
请求拦截器:请求拦截器的作用是在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易
-
响应拦截器:响应拦截器的作用是在接收到响应后进行一些操作,例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页
axios.interceptors.request.use(function(config) { console.log('config: ', config); console.log('url: ', config.url); if (config.url.indexOf('/data-json1') !== -1) { config.headers.mytoken = 'xxxx'; } return config }); axios.interceptors.response.use(function(res) { console.log('response: ', res); if (res.request.responseURL.indexOf('/data-json1') !== -1) { return res.data; } return res; }, function(err) { console.log('err: ', err); }); axios.get('http://localhost/data-json1') .then(function(res) { console.log('data: ', res); });
接口调用-async/await用法
async/await是ES7引入的新语法,它的实现是基于Promise,可以更加方便的进行异步操作:
-
async 关键字用于函数上(任何一个 async 函数都会隐式返回一个 promise)
-
await 关键字用于 async 函数当中,await函数不能单独使用(await可以得到异步的结果,也可以直接跟一个 Promise实例对象)
async function queryData() { var ret = await axios.get('http://localhost/data/1') return ret.data; } queryData().then(function(data) { console.log('data: ', data); }); async function queryData() { var ret = await new Promise(function(resolve, reject){ setTimeout(() => { resolve('100'); }, 1000); }); return ret; } queryData().then(function(res) { console.log('res: ', res); });
async/await 让异步代码看起来、表现起来更像同步代码,多个异步请求的场景:
async function queryData() {
var info = await axios.get('http://localhost/data/1');
var ret = await axios.get('http://localhost/data?id=' + info.data.id);
return ret.data;
}
queryData().then(function(data) {
console.log('data: ', data);
})
综合案例------(基于后台接口的图书管理案例)
axios 全局配置以及配置响应拦截器:
// 全局配置 默认地址
axios.defaults.baseURL = 'http://localhost:80/';
// 响应拦截器
axios.interceptors.response.use(function(res) {
return res.data;
});
加载图书列表
使用 async、await 封装 queryData 函数获取后台数据:
queryData: async function() {
this.books = await axios.get('books')
}
删除 mounted 钩子函数中的假数据,调用获取后台接口数据的函数 queryData:
mounted: function() {
// 该生命周期钩子函数被触发的时候,模板已经可以使用
// 一般此时用于获取后台数据,然后把数据填充到模板
this.queryData();
}
其他功能实现
验证图书名是否可用:
watch: {
bname: async function(n) {
if (n.length > 0) {
let ret = await axios.get('books/check/' + n);
console.log('ret: ', ret);
this.confirm = !ret.status;
} else {
this.confirm = false;
}
}
}
去编辑图书:
editHandle: async function(m) {
let ret = await axios.get('books/' + m.id);
if (ret !== null) {
this.bid = ret.id;
this.bname = ret.name;
this.flag = false;
}
}
添加、编辑图书:
addHandle: async function() {
console.log('addHandle');
if (this.flag) { // 添加
let ret = await axios.post('books', {
name: this.bname
})
// 成功重新加载数据
if (ret.code == 200) {
this.queryData();
}
} else { // 编辑
let ret = await axios.put('books', {
id: this.bid,
name: this.bname
});
// 成功重新加载数据
if (ret.code == 200) {
this.queryData();
}
this.flag = true;
}
this.bid = '';
this.bname = '';
}
删除图书:
deleteHandle: async function(m) {
let ret = await axios.delete('books/' + m.id);
// 成功重新加载数据
if (ret.code == 200) {
this.queryData();
}
}
图书管理后台实现
新建后台项目文件夹 books, 安装 express、cros 这两个包。
新建 handle.js 文件处理接口:
// 处理文件
const path = require('path');
const fs = require('fs');
const data = require('./data.json');
// 生成图书id
function generateBookId() {
return Math.max.apply(null, data.map(function(v) {
return v.id;
})) + 1;
}
// 写入数据
function writeData(res) {
fs.writeFile(path.join(__dirname, 'data.json'), JSON.stringify(data), function(err) {
if (err) {
res.json({
code: 500,
err: err
});
} else {
res.json({
code: 200,
msg: '成功'
});
}
})
}
// 获取图书
function books(req, res) {
res.send(data);
}
// 添加图书
function addBook(req, res) {
const body = req.body;
const book = {
id: generateBookId(),
name: body.name,
date: + new Date()
};
console.log('book: ', book);
data.unshift(book);
writeData(res);
}
// 获取单个图书
function oneBook(req, res) {
console.log('params: ', req.params);
const id = req.params.id;
let book = null;
data.some(function(v, i, a) {
console.log('v: ', v);
if (v.id == id) {
book = v;
return true;
}
});
console.log('book: ', book);
res.json(book);
}
// 编辑图书
function editBook(req, res) {
const body = req.body;
console.log('body: ', body);
data.some(function(v, i, a) {
if (body.id == v.id) {
v.name = body.name;
}
});
writeData(res);
}
// 删除图书
function deleteBook(req, res) {
console.log('params: ', req.params);
const id = req.params.id;
data.some((v, i, a) => {
if (v.id == id) {
data.splice(i, 1);
}
});
writeData(res);
}
// 验证图书名是否存在
function checkName(req, res) {
const name = req.params.name;
let result = false;
data.some(function(v, i, a) {
result = name == v.name;
return result;
});
res.json({
status: result
});
}
module.exports = {
books,
addBook,
oneBook,
editBook,
deleteBook,
checkName
}
新建 router.js 文件,处理路由:
// 路由模块
let express = require('express');
let router = express.Router();
let handle = require('./handle');
// 获取图书
router.get('/books', handle.books);
// 添加图书
router.post('/books', handle.addBook);
// 获取单个图书
router.get('/books/:id', handle.oneBook);
// 编辑图书
router.put('/books', handle.editBook);
// 删除图书
router.delete('/books/:id', handle.deleteBook);
// 验证图书名是否存在
router.get('/books/check/:name', handle.checkName);
module.exports = router;
新建 index.js 文件,处理服务:
// 导入 express
const express = require('express');
// 创建服务器
const app = express();
// 配置 cors 跨域中间件
const cors = require('cors');
app.use(cors());
// 配置解析 application/x-www-form-urlencoded 格式的表单数据的中间件
app.use(express.urlencoded({ extended: false }));
// 配置解析表单中的 JSON 格式的数据的中间件
app.use(express.json());
// 启动静态资源服务
app.use(express.static('public'))
// 配置路由
let router = require('./router');
app.use(router);
// 指定端口,启动服务器
app.listen(80, function(){
console.log('server running at http://127.0.0.1:80');
});
附录
附录一
示例 node 后台代码:
// 导入模块
const express = require('express');
// 创建服务器
const app = express();
// 配置跨域中间件
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
// 配置自定义请求头
app.use('/data-json', function(req, res, next) {
res.header('Access-Control-Allow-Headers', 'mytoken');
next();
});
app.use('/data-json1', function(req, res, next) {
res.header('Access-Control-Allow-Headers', 'mytoken');
next();
});
// 配置数据解析中间件
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// 监听请求
app.get('/data1', function(req, res) {
console.log('----data1----');
res.send('data1');
});
app.get('/data2', function(req, res) {
console.log('----data2----');
res.send('data2');
});
app.get('/data3', function(req, res) {
console.log('----data3----');
res.send('data3');
});
app.get('/data', function(req, res) {
console.log(req.query);
res.send({
method: 'get',
...req.query
});
});
app.get('/data/:id', function(req, res) {
console.log(req.params);
res.send({
method: 'get',
...req.params
});
});
app.post('/data', function(req, res) {
console.log(req.body);
res.send({
method: 'post',
...req.body
});
});
app.delete('/data', function(req, res) {
console.log(req.params);
res.send({
method: 'delete',
...req.query
});
});
app.delete('/data/:id', function(req, res) {
console.log(req.params);
res.send({
method: 'delete',
...req.params
});
});
app.put('/data/:id', function(req, res) {
console.log(req.params);
console.log(req.body);
res.send({
method: 'put',
id: req.params.id,
...req.body
});
});
app.get('/data-json', (req, res) => {
console.log(req.headers);
res.json({
uname: '赵六',
age: 12
});
});
app.get('/data-json1', (req, res) => {
console.log(req.headers);
res.json({
uname: '赵六',
age: 12
});
});
// 调用 app.listen(端口号, 启动成功后的回调函数),启动服务器
app.listen(80, function() {
console.log('server running at http://127.0.0.1:80');
});