express框架是一个用node写的后台搭建框架,npm官网可以查阅到对应文档。
翻译一下主要特点:
有了express我们就可以相较与手写纯node,更快更简单的创建一个服务器服务;
这里先记录不使用脚手架的使用方式。
安装express
npm i express
引入:
const express = require('express')
实例化:
var app = express() app.listen(3000) // 监听端口
例子来源极客时间
index.html
<html> <head> <meta charset="utf-8" /> <style> * { padding: 0; margin: 0; } button { display: inline-block; } </style> </head> <body> <div id="output" style="height: 400px; width: 600px; background: #eee" ></div> <button id="rock" style="height: 40px; width: 80px">石头</button> <button id="scissor" style="height: 40px; width: 80px">剪刀</button> <button id="paper" style="height: 40px; width: 80px">布</button> </body> <script> const $button = { rock: document.getElementById("rock"), scissor: document.getElementById("scissor"), paper: document.getElementById("paper"), }; const $output = document.getElementById("output"); // 给每个按钮监听他们的点击事件 Object.keys($button).forEach((key) => { $button[key].addEventListener("click", function () { fetch(`http://${location.host}/game?action=${key}`) .then((res) => { return res.text(); }) .then((text) => { $output.innerHTML += text + "<br/>"; }); }); }); </script> </html>
game.js
module.exports = function (playerAction) { // 如果玩家输入的不是石头剪刀布就报错 if (['rock', 'scissor', 'paper'].indexOf(playerAction) == -1) { throw new Error('invalid playerAction'); } // 计算电脑出的东西 var computerAction; var random = Math.random() * 3 if (random < 1) { computerAction = 'rock' } else if (random > 2) { computerAction = 'scissor' } else { computerAction = 'paper' } if (computerAction == playerAction) { // 如果电脑和玩家出的一样 return 0; } else if ( // 如果玩家输 (computerAction == 'rock' && playerAction == 'scissor') || (computerAction == 'scissor' && playerAction == 'paper') || (computerAction == 'paper' && playerAction == 'rock') ) { return -1; } else { // 如果玩家赢 return 1; } }
index.js:
const fs = require('fs'); const game = require('./game') const express = require('express'); var playerWinCount = 0 // 玩家胜利次数,如果超过3,则后续往该服务器的请求都返回500 var lastPlayerAction = null; // 玩家的上一次游戏动作 var sameCount = 0; // 玩家连续出同一个动作的次数 const app = express(); // 通过app.get设定 /favicon.ico 路径的路由 // .get 代表请求 method 是 get,所以这里可以用 post、delete 等。这个能力很适合用于创建 rest 服务 app.get('/favicon.ico', function (request, response) { // 一句 status(200) 代替 writeHead(200); end(); response.status(200) return; }) // 设定 /game 路径的路由 app.get('/game', function (request, response, next) { if (playerWinCount >= 3 || sameCount == 9) { response.status(500); response.send('我不会再玩了!'); return; } // 通过next执行后续中间件 next(); // 当后续中间件执行完之后,会执行到这个位置,如果最后的中间件开启异步执行,response.playerWon就等于undefined if (response.playerWon) { playerWinCount++; } }, function (request, response, next) { // express自动帮我们把query处理好挂在request上 const query = request.query; const playerAction = query.action; if (!playerAction) { response.status(400); response.send(); return; } if (lastPlayerAction == playerAction) { sameCount++ if (sameCount >= 3) { response.status(400); response.send('你作弊!我再也不玩了'); sameCount = 9 return; } } else { sameCount = 0; } lastPlayerAction = playerAction; // 把用户操作挂在response上传递给下一个中间件 response.playerAction = playerAction next(); }, function (req, response) { const playerAction = response.playerAction; const result = game(playerAction); // 如果这里执行setTimeout,会导致前面的洋葱模型失效 // 因为playerWon不是在中间件执行流程所属的那个事件循环里赋值的 // setTimeout(()=> { response.status(200); if (result == 0) { response.send('平局') } else if (result == -1) { response.send('你输了') } else { response.send('你赢了') response.playerWon = true; } // }, 500) } ) app.get('/', function (request, response) { // send接口会判断你传入的值的类型,文本的话则会处理为text/html // Buffer的话则会处理为下载 response.send( fs.readFileSync(__dirname + '/index.html', 'utf-8') ) }) app.listen(3000);
上面代码也看到了,next()
可以让代码的执行可以从一个回调跳到另一个回调然后再回来接着执行,看起来像一层一层的深入调用,所以叫洋葱模型。中间的转跳类似半路去做其他事情,所以可以称其他事情为中间件。
另外,express的洋葱模型有局限性,当中间件做一些异步操作的话,就会新起一个执行栈成员,这样在例子中给response挂载的playerWon属性就不是最外层的response上了。