NodeJS_06

 

NodeJS七天课程学习笔记_第6天

课程内容概要:

1.  Node 中如何操作Mysql数据库 (包括如何使用连接池pool)

2. 将上一个使用MongoDB的CRUB项目改写成使用Mysql

3. 针对回调地狱callback hell 而生的 Promise语法

4. 封装promise版本的自定义ajax的get方法

5. 演示一下promise的使用场景

6. 将前面的node_36_index.js这个mongoose的CRUD案例中的写法改成promise+then的写法

在npmjs.com中搜索mysql,进入使用介绍页面:https://www.npmjs.com/package/mysql

安装mysql:

npm install mysql –save

然后启动本机的Mysql,看一下db2库下面有什么表

根据文档写出node操作mysql的最简单的demo

node_37.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 1.导入框架
var mysql = require('mysql')
// 2.创建连接
var connection = mysql.createConnection({
	host: 'localhost',
	user: 'root',
	password: '520',
	database: 'db2'
})
// 3.连接数据库
connection.connect();
// 4.查询数据库
var sql = 'select 6+7 as girlAge'
connection.query(sql,function (error,results,fields) {
	if(error){
		return NSLog(error)
	}
	NSLog('girl is : ' + results[0].girlAge + '岁')
})
// 5.关闭数据库
connection.end()

效果如下:

下面尝试着把user表里的第一条记录查出来:

node_37_query1.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 1.导入框架
var mysql = require('mysql')
// 2.创建连接
var connection = mysql.createConnection({
	host: 'localhost',
	user: 'root',
	password: '520',
	database: 'db2'
})
// 3.连接数据库
connection.connect();
// 4.查询数据库
var sql = 'select name from user'
connection.query(sql,function (error,results,fields) {
	if(error){
		return NSLog(error)
	}
	NSLog('girl is : ' + results[0].name)
})
// 5.关闭数据库
connection.end()

效果如下:

通过软件查看本机数据库中的表和记录,这里使用Navicat Premium

打开Navicat Premium12.0.20之后,新建一个连接,如图所示:

然后就可以,连接本机mysql数据库,查看db2数据库里的user表了:

手动新建一个girls的表: (输入完字段名和类型后,Ctrl + S保存,输入表名)

补充一下: 为了安全起见, mysql中没有直接修改 数据库的名字的命令

现在通过node_37_add.js我们来插入一条记录到数据库,代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 1.导入框架
var mysql = require('mysql')
// 2.创建连接
var connection = mysql.createConnection({
	host: 'localhost',
	user: 'root',
	password: '520',
	database: 'db2'
})
// 3.连接数据库
connection.connect();
// 4.查询数据库
// 如果不指定字段名,则每一个都要填写值(主键可以用null代替)
var sql = 'insert into girls values(null,"面码",15,"未闻花名","2010-06-07")'
connection.query(sql,function (error,results,fields) {
	if(error){
		return NSLog(error)
	}
	NSLog('插入成功')
})
// 5.关闭数据库
connection.end()

效果如下:

多插入几个,再查询一下:

接下来根着文档写根据_id查询 node_37_findOneById.js代码如下:

(因为后面用Mysql重写CRUD项目中,根据_id进行查询出来后,进入修改页面)

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 1.导入框架
var mysql = require('mysql')
// 2.创建连接
var connection = mysql.createConnection({
	host: 'localhost',
	user: 'root',
	password: '520',
	database: 'db2'
})
// 3.连接数据库
connection.connect();
// 4.查询数据库
var sql = 'select girlName,girlAge,girlDescription,pubTime from girls where _id = ?'
var queryObj = {
				'sql': sql,
				timeout: 6000,
				values: ['2']
				}
connection.query(queryObj,function (error,results,fields) {
	if(error){
		return NSLog(error)
	}
	var girlObj = results[0]
	NSLog('根据_id = 2 查询成功: \n' + girlObj.girlName + ',' + girlObj.girlAge + ',' + girlObj.girlDescription + ',' + girlObj.pubTime)
})
// 5.关闭数据库
connection.end()

效果如下:

根据_id,删除一条记录node_37_deleteOneById.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 1.导入框架
var mysql = require('mysql')
// 2.创建连接
var connection = mysql.createConnection({
	host: 'localhost',
	user: 'root',
	password: '520',
	database: 'db2'
})
// 3.连接数据库
connection.connect();
// 4.查询数据库
var sql = 'delete from girls where _id = ?'
var queryObj = {
				'sql': sql,
				timeout: 6000,
				values: ['5']
				}
connection.query(queryObj,function (error,results,fields) {
	if(error){
		return NSLog(error)
	}
	NSLog('删除成功')
})
// 5.关闭数据库
connection.end()

效果如下:

查询所有的记录node_37_findAll.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 1.导入框架
var mysql = require('mysql')
// 2.创建连接
var connection = mysql.createConnection({
	host: 'localhost',
	user: 'root',
	password: '520',
	database: 'db2'
})
// 3.连接数据库
connection.connect();
// 4.查询数据库
var sql = 'select _id,girlName,girlAge,girlDescription,pubTime from girls'
connection.query(sql,function (error,results,fields) {
	if(error){
		return NSLog(error)
	}
	// 遍历,打印
	NSLog("查询成功",false)
	for(var i = 0;i < results.length; i++){
		var girlObj = results[i]
		NSLog(girlObj._id + ', ' + girlObj.girlName + ', ' + girlObj.girlAge + '岁, 「' + girlObj.girlDescription + '」, ' + girlObj.pubTime,false)
	}
 
})
// 5.关闭数据库
connection.end()

效果如下:

更新一条记录node_37_update.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 1.导入框架
var mysql = require('mysql')
// 2.创建连接
var connection = mysql.createConnection({
	host: 'localhost',
	user: 'root',
	password: '520',
	database: 'db2'
})
// 3.连接数据库
connection.connect();
// 4.查询数据库
var sql = 'update girls set girlName = ?,girlAge = ?,girlDescription = ? where _id = ?'
var queryObj = {
				'sql': sql,
				timeout: 6000,
				values: ['面码',13,'未闻花名','1']
				}
connection.query(queryObj,function (error,results,fields) {
	if(error){
		return NSLog(error)
	}
	NSLog('更新成功')
})
// 5.关闭数据库
connection.end()

效果如下:

下面的node_37_poolFindAll.js 将演示一下

如何在Node中使用Mysql的 连接池 Pool Connection :

(因为每次查询都要连接然后断开的话 会非常消耗CPU资源)

(如果每一个用户连接后,又不断开,那么会不断地消耗内存资源)

官方示例代码如下:

npmjs.com/package/mysql#pooling-connections

node_37_poolFindAll.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 1.导入框架
var mysql = require('mysql')
// 2.创建连接
var pool = mysql.createPool({
	connectionLimit: 10,
	host: 'localhost',
	user: 'root',
	password: '123456',
	database: 'db2'
})
// 3.直接查询数据库
var sql = 'select _id,girlName,girlAge,girlDescription,pubTime from girls'
pool.query(sql,function (error,results,fields) {
	if(error){
		return NSLog(error)
	}
	// 遍历,打印
	NSLog("查询成功",false)
	for(var i = 0;i < results.length; i++){
		var girlObj = results[i]
		NSLog(girlObj._id + ', ' + girlObj.girlName + ', ' + girlObj.girlAge + '岁, 「' + girlObj.girlDescription + '」, ' + girlObj.pubTime,false)
	}
	console.log("\nCopyright © 2018 Powered by beyond")
})
// 无需关闭,查询完毕会自动进行连接池 pool

效果如下:

下面把node_36_index.js这个使用MongoDB的CRUD项目,转换成使用Mysql的node_38_index.js项目

项目目录如下:

默认的views目录下的index目录下的3个html模板文件如下:

效果如下:

app入口文件node_38_index.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  


// 导入框架
var express = require('express')
// 创建服务器对象
var appServer = express()
// 监听端口,并启动服务
appServer.listen(5267,function (error) {
	if (error) {
		return NSLog('启动失败: ' + error)
	}
	NSLog('服务启动成功')
})
// -----------------------------------

// 静态资源请求时的 staticFileUrlPrefix
var staticFileUrlPrefix = '/public/'
// var staticFileUrlPrefix = '/public'

// 访问也只能使用 localhost:5267/public/img/beyond.jpg
// 磁盘上的静态资源目录
var staticFilePath = './public/'   
// var staticFilePath = 'public' 

var callbackFunction = express.static(staticFilePath)
appServer.use(staticFileUrlPrefix,callbackFunction)

// -----------------------------------

// 指明:对于 所有后缀为html 的模板文件 使用模板引擎
var templateFileSuffix = 'html'
appServer.engine(templateFileSuffix,require('express-art-template'))
// 下面这一句参数配置,可有可无
appServer.set('view options',{
	debug: process.env.NODE_ENV !== 'production'
})
// 注意:如果不想把模板文件放在默认的views目录下,则可以通过下面代码更改设置
// appServer.set('views','其他目录')

// -----------------------------------
// 使用middleware中间件body-parser进行post请求体中数据解析
var bodyParser = require('body-parser')
// 设置解析 application/x-www-form-urlencoded
appServer.use(bodyParser.urlencoded({extended: false}))        
// 设置解析 application/json
appServer.use(bodyParser.json())
// -----------------------------------
// 自定义路由设计的目的是:
// 1.让主入口程序的职责更加单一,代码更加简洁
//     1.1 创建服务
//     1.2 做一些服务相关的配置,比如:
//           1.2.1 静态资源配置
//           1.2.2 模板引擎配置
//           1.2.3 body-parse 解析表单
//           1.2.4 挂载自定义路由
//           1.2.5 监听端口,启动服务
// 使用自定义的路由模块 必须使用./
// 注意: 配置模板引擎和body-parser, 一定要在挂载路由之前
var beyondRouter = require('./node_38_router')
appServer.use(beyondRouter)

路由模块代码node_38_router.js代码如下:

function NSLog(loli) {console.log(loli);return 'Copyright © 2018 Powered by beyond';};  
/*
	自定义路由模块的职责是:
		专门处理所有的路由
		根据不同的请求方式和路径,采取相应的处理方法
*/ 
// express 专门提供了路由的处理方法
var express = require('express')
// 1.使用express专门提供的路由器处理路由
var router = express.Router()

// ----------------引入dao模块-------------------
// 先对dao初始化
var poolDao = require('./node_38_dao')
// 时间格式化
var BeyondDateFormatFunction = require('./BeyondDateFormat')

// ----------------首页-------------------
router.get('/',function (request,response) {
	// 至于请求参数可以这样:
	// var queryObj = request.query

	// ----------------查找所有------------------
	var sql = 'select _id,girlName,girlAge,girlDescription,pubTime from girls'
	poolDao.query(sql,function (error,results,fields) {
		if(error){
			return response.send(error)
		}
		// 使用模板引擎渲染
		// 注意: 模板文件默认是放在views目录下
		// 为此,我们在views目录下  分别为不同的业务模块创建了不同的文件夹
		// 如 login登录 admin后台管理 index前台首页 article文章 comment评论
		response.render('index/node_38_index.html',{girlArr:results})
	})
	// 无需关闭,查询完毕会自动进行连接池 pool

})

// ----------------添加的表单页面-------------------
router.get('/add',function (request,response) {
	response.render('index/node_38_add.html')
})


// ----------------增加一条记录-------------------
router.post('/insert',function (request,response) {
	// 1.body-parser得到obj
	var girlObj = request.body
	// 1.调用girlDao写到文件数据库
	girlObj.pubTime = BeyondDateFormatFunction(new Date(),'yyyy-MM-dd')
	// 2.保存到数据库
	// 如果不指定字段名,则每一个都要填写值(主键可以用null代替)
	var sql = 'insert into girls values(null,?,?,?,?)'
	var queryObj = {
				'sql': sql,
				timeout: 6000,
				values: [girlObj.girlName,girlObj.girlAge,girlObj.girlDescription,girlObj.pubTime]
				}
	poolDao.query(queryObj,function (error,results,fields) {
		if(error){
			// 有错误
			return response.status(500).send(error)
		}
		// 没有错误,跳转到首页	
		response.redirect('/')
	})
})

// ----------------修改页面------------------- 
router.get('/edit',function (request,response) {
	var sql = 'select _id,girlName,girlAge,girlDescription,pubTime from girls where _id = ?'
	var queryObj = {
					'sql': sql,
					timeout: 6000,
					values: [request.query._id]
					}
	poolDao.query(queryObj,function (error,results,fields) {
		if(error){
			// 如果出错
			return response.status(500),send(error)
		}
		// 如果查询成功
		response.render('index/node_38_edit.html',{'girl': results[0]})
		
	})
})

// ----------------更新数据库------------------- 
router.post('/update',function (request,response) {
	// 1.请求体 id号 (前后多了两个引号,要手动去掉)
	var girlID = request.body._id
	var girlName = request.body.girlName
	var girlAge = request.body.girlAge
	var girlDescription = request.body.girlDescription
	// 2.更新
	var sql = 'update girls set girlName = ?,girlAge = ?,girlDescription = ? where _id = ?'
	var queryObj = {
					'sql': sql,
					timeout: 6000,
					values: [girlName,girlAge,girlDescription,girlID]
					}
	// 使用连接池更新记录					
	poolDao.query(queryObj,function (error,results,fields) {
		if(error){
			// 如果失败
			return response.status(500).send(error)
		}
		// 如果成功
		response.redirect('/')
	})
})

// ----------------删除一条记录------------------- 
router.get('/delete',function (request,response) {
	// 1.获取query对象中的_id
	var girlID = request.query._id
	
	// 2.调用dao从数据库中删除一个对象
	var sql = 'delete from girls where _id = ?'
	var queryObj = {
					'sql': sql,
					timeout: 6000,
					values: [girlID]
					}
	// 使用连接池					
	poolDao.query(queryObj,function (error,results,fields) {
		if(error){
			// 如果错误
			return response.status(500).send(error)
		}
		// 如果没有错误,跳转到首页
		return response.redirect('/')
	})
})


// 3.在模块文件最后,导出router
module.exports = router

Dao数据库操作文件node_38_dao.js代码如下:

function NSLog(loli) {console.log(loli);return 'Copyright © 2018 Powered by beyond';};  

// 1.导入框架
var mysql = require('mysql')
// 2.创建连接
var pool = mysql.createPool({
	connectionLimit: 10,
	host: 'localhost',
	user: 'root',
	password: '123456',
	database: 'db2'
})

// 3.无需关闭,查询完毕会自动进行连接池 pool

// 4.导出连接池
module.exports = pool

三个html文件,代码与前一篇文章node_36_index(add和edit).html几乎一模一样,这儿就不重复显示了

回调地狱callback hell

先上一张经典的图:

演示读取失败时的node_39.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 回调地狱的解决方案演示 Promise

var fs = require('fs')
// 在ECMAScript6中,新增了一个API Promise
// Promise是一个构造函数,目的就是避免回调地狱 callback hell
// 通过new + 构造函数 创建一个Promise
// 构造函数的参数 是一个匿名函数(block代码块)
// Promise一旦创建,就会立即执行参数(即匿名函数block)里面的代码
NSLog('0_before_promise',false)
var promise_1 = new Promise(function () {
	// Promise本身是同步顺序执行的,但是它里面的异步操作是异步执行的
	NSLog('1_in_promise',false)
	fs.readFile('node_39_1.txt','utf8',function (error,data) {
		NSLog('3_in_promise_block',false)
		if (error) {
			// 如果失败,则承诺容器中的异步任务失败了
			NSLog('读取失败')
		}else{
			// 如果成功,则承诺容器中的异步任务成功了
			NSLog('读取成功: ' + data)
		}
	})
})
NSLog('2_after_promise',false)

效果如下:

读取成功时的效果如下:

注意: Promise容器中往往通过构造函数时的参数,传入一个匿名函数(block代码块), 这个block代码块会被立刻执行,
并且,这个block代码块中,往往封装的都是一些异步操作任务

Promise在初始状态时,只是Pending状态
一旦block代码块中的异步操作完成之时,Promise的状态就会发生改变.

当block代码块中的异步操作成功时,Promise状态变成Resolved
当block代码块中的异步操作失败时,Promise状态变成Rejected

示例图如下:

接下来要进入重头戏了,

下面给promise构造函数中的匿名函数(block代码块)写上成功时回调的参数1和失败时回调的参数2

并在异步任务完成时,回调对应的函数

上面的成功时回调的参数1和失败时回调的参数2 对应于 then方法的 参数1 和参数2

下面演示一下promise对象的then方法,以及then方法中的两个参数

node_40.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 回调地狱的解决方案演示 Promise
var fs = require('fs')
// 在ECMAScript6中,新增了一个API Promise
// Promise是一个构造函数,目的就是避免回调地狱 callback hell
// 通过new + 构造函数 创建一个Promise
// 构造函数的参数 是一个匿名函数(block代码块)
// Promise一旦创建,就会立即执行参数(即匿名函数block)里面的代码
// 匿名函数block有两个参数是用于回调的,它们分别是resolveCallBack和rejectCallback
// 对应于then方法的参数1和参数2
NSLog('0_before_promise',false)
var promise_1 = new Promise(function (resolveCallback,rejectCallback) {
	// Promise本身是同步顺序执行的,但是它里面的异步操作是异步执行的
	NSLog('1_in_promise',false)
	fs.readFile('node_39_1.txt','utf8',function (error,successData) {
		NSLog('5_in_promise_block',false)
		if (error === null) {
			// 如果成功,则承诺容器中的异步任务成功了
			// 成功回调函数(即then方法的参数1)
			resolveCallback(successData)
		}else{
			// 如果失败,则承诺容器中的异步任务失败了
			// NSLog('读取失败' + error)
			// 失败回调函数(即then方法的参数2)
			rejectCallback(error)
		}
	})
})

NSLog('2_after_promise',false)
NSLog('3_before_then',false)

promise_1.then(function (successData) {
	NSLog('6.读取成功: ' + successData)
},function (error) {
	NSLog('6.读取失败: ' + error)
})
NSLog('4_after_then',false)

效果如下:

示意图如下:

关于 在then的参数(即匿名函数)中返回值 的说明
这个返回值类型 只有两种情况
第1种情况: 返回一个promise对象 (最常见)
那么下一个then中,参数1就是promise对象的resolveCallback
参数2就是promise对象的rejectCallback
第2种情况: 返回一个非Promise的对象 (极少用到)
比如返回一个String字符串 “未闻花名”
那么,在下一个then中就只有一个匿名函数callback作为参数
那个匿名函数callback中的参数 就是这儿的返回值 即String的值  “未闻花名”

下面再进一步演示一下,如果在then中返回一个字符串会怎么样

node_42.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 回调地狱的解决方案演示 Promise

var fs = require('fs')
// 在ECMAScript6中,新增了一个API Promise
// Promise是一个构造函数,目的就是避免回调地狱 callback hell
// 通过new + 构造函数 创建一个Promise
// 构造函数的参数 是一个匿名函数(block代码块)
// Promise一旦创建,就会立即执行参数(即匿名函数block)里面的代码
// 匿名函数block有两个参数是用于回调的,它们分别是resolveCallBack和rejectCallback
// 对应于then方法的参数1和参数2
NSLog('0_before_promise_1',false)
var promise_1 = new Promise(function (resolveCallback,rejectCallback) {
	// Promise本身是同步顺序执行的,但是它里面的异步操作是异步执行的
	NSLog('1_in_promise_1',false)
	fs.readFile('node_40_1.txt','utf8',function (error,successData) {
		if (error === null) {
			// 如果成功,则承诺容器中的异步任务成功了
			// 成功回调函数(即then方法的参数1)
			resolveCallback(successData)
		}else{
			// 如果失败,则承诺容器中的异步任务失败了
			// NSLog('读取失败' + error)
			// 失败回调函数(即then方法的参数2)
			rejectCallback(error)
		}
	})
})


NSLog('2_after_promise',false)
NSLog('3_before_then',false)

promise_1.then(function (successData) {
	NSLog('5.读取txt成功: \n' + successData,false)
	/*
	关于这个返回值 要说明一下
	这个返回值类型 只有两种情况
	第1种情况: 返回一个promise对象
			  那么下一个then中,参数1就是promise对象的resolveCallback
			  参数2就是promise对象的rejectCallback
	第2种情况: 返回一个非Promise的对象 (极少用到)	
			  比如返回一个String字符串	 "那朵花"
			  那么,在下一个then中就只有一个匿名函数callback作为参数
			  那个匿名函数callback中的参数 就是这儿的返回值 即String的值  "那朵花"	
	*/ 
	return "未闻花名"
},function (error) {
	NSLog('5.读取txt失败: \n' + error,false)
	/*
	关于这个返回值 要说明一下
	这个返回值类型 只有两种情况
	第1种情况: 返回一个promise对象
			  那么下一个then中,参数1就是promise对象的resolveCallback
			  参数2就是promise对象的rejectCallback
	第2种情况: 返回一个非Promise的对象 (极少用到)	
			  比如返回一个String字符串	 "那朵花"
			  那么,在下一个then中就只有一个匿名函数callback作为参数
			  那个匿名函数callback中的参数 就是这儿的返回值 即String的值  "那朵花"	
	*/ 
	return "anohana"
}).then(function (previousThenReturnData) {
	NSLog('6.这个是上一个then中的返回值: \n   ' + previousThenReturnData)	
})
NSLog('4_after_then',false)

成功时效果如下:

失败时效果如下:

下面最核心最精华的部分了, 我们使用Promise的目的是避免回调地狱的同时,又要保证N个异步任务的结果,按顺序输出

比如下面的promise_1和promise_2和promise_3分别读取a,b,c三个txt方法,并按顺序输出其内容

在这儿我们就需要在promise_1的then方法中 输出了a.txt内容之后,return promise_2,

这样我们就可以继续调用promise_2的then方法中 输出b.txt的内容,

输出了b.txt内容之后同样的,我们返回promise_3对象,

这样我们就可以继续调用promise_3的then方法中 输出c.txt的内容

示例代码node_41.js如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 回调地狱的解决方案演示 Promise

var fs = require('fs')
// 在ECMAScript6中,新增了一个API Promise
// Promise是一个构造函数,目的就是避免回调地狱 callback hell
// 通过new + 构造函数 创建一个Promise
// 构造函数的参数 是一个匿名函数(block代码块)
// Promise一旦创建,就会立即执行参数(即匿名函数block)里面的代码
// 匿名函数block有两个参数是用于回调的,它们分别是resolveCallBack和rejectCallback
// 对应于then方法的参数1和参数2
NSLog('0_before_promise_1',false)
var promise_1 = new Promise(function (resolveCallback,rejectCallback) {
	// Promise本身是同步顺序执行的,但是它里面的异步操作是异步执行的
	NSLog('1_in_promise_1',false)
	fs.readFile('node_40_1.txt','utf8',function (error,successData) {
		
		if (error === null) {
			// 如果成功,则承诺容器中的异步任务成功了
			// 成功回调函数(即then方法的参数1)
			resolveCallback(successData)
		}else{
			// 如果失败,则承诺容器中的异步任务失败了
			// NSLog('读取失败' + error)
			// 失败回调函数(即then方法的参数2)
			rejectCallback(error)
		}
	})
})

var promise_2 = new Promise(function (resolveCallback,rejectCallback) {
	// Promise本身是同步顺序执行的,但是它里面的异步操作是异步执行的
	NSLog('2_in_promise_2',false)
	fs.readFile('node_40_2.txt','utf8',function (error,successData) {
		
		if (error === null) {
			// 如果成功,则承诺容器中的异步任务成功了
			// 成功回调函数(即then方法的参数1)
			resolveCallback(successData)
		}else{
			// 如果失败,则承诺容器中的异步任务失败了
			// NSLog('读取失败' + error)
			// 失败回调函数(即then方法的参数2)
			rejectCallback(error)
		}
	})
})

var promise_3 = new Promise(function (resolveCallback,rejectCallback) {
	// Promise本身是同步顺序执行的,但是它里面的异步操作是异步执行的
	NSLog('3_in_promise_3',false)
	fs.readFile('node_40_3.txt','utf8',function (error,successData) {
		
		if (error === null) {
			// 如果成功,则承诺容器中的异步任务成功了
			// 成功回调函数(即then方法的参数1)
			resolveCallback(successData)
		}else{
			// 如果失败,则承诺容器中的异步任务失败了
			// NSLog('读取失败' + error)
			// 失败回调函数(即then方法的参数2)
			rejectCallback(error)
		}
	})
})

NSLog('4_after_promise_3',false)
NSLog('5_before_then',false)

promise_1.then(function (successData) {
	NSLog('10.读取1.txt成功: \n' + successData,false)
	return promise_2
},function (error) {
	NSLog('10.读取1.txt失败: \n' + error)
	return promise_2
}).then(function (successData) {
	NSLog('11.读取2.txt成功: \n' + successData,false)
	return promise_3
},function (error) {
	NSLog('11.读取2.txt失败: \n' + error)
	return promise_3
}).then(function (successData) {
	NSLog('12.读取3.txt成功: \n' + successData)
	
},function (error) {
	NSLog('12.读取3.txt失败: \n' + error)
})
NSLog('6_after_then',false)

效果如下:

注意:  之所有 没有 7 8 9 ,是因为 三个block代码块中的异步操作 谁先完成  这个顺序是不确定的

示意图如下:

下面演示了自定义一个函数,参数是 文件路径,返回值是一个Promise

node_43.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 回调地狱的解决方案演示 Promise
var fs = require('fs')
// 自己封装一个readFile方法,参数是: filePath
// 返回值是: 一个promise
function beyondReadFile (filePath) {
	// 在ECMAScript6中,新增了一个API Promise
	// Promise是一个构造函数,目的就是避免回调地狱 callback hell
	// 通过new + 构造函数 创建一个Promise
	// 构造函数的参数 是一个匿名函数(block代码块)
	// Promise一旦创建,就会立即执行参数(即匿名函数block)里面的代码
	// 匿名函数block有两个参数是用于回调的,它们分别是resolveCallBack和rejectCallback
	// 对应于then方法的参数1和参数2
	var promise_1 = new Promise(function (resolveCallback,rejectCallback) {
		// Promise本身是同步顺序执行的,但是它里面的异步操作是异步执行的
		fs.readFile(filePath,'utf8',function (error,successData) {
			if (error === null) {
				// 如果成功,则承诺容器中的异步任务成功了
				// 成功回调函数(即then方法的参数1)
				resolveCallback(successData)
			} else{
				// 如果失败,则承诺容器中的异步任务失败了
				// NSLog('读取失败' + error)
				// 失败回调函数(即then方法的参数2)
				rejectCallback(error)
			}
		})
	})
	// 返回promise,目的是为了能够链式使用then语法
	return promise_1
}

// 调用一下,因为返回值是promise,所以可直接then
beyondReadFile('node_40_1.txt')
	.then(function (successData) {
		NSLog('读取1.txt成功:\n   ' + successData)
		// 见下一个示例
	},function (error) {
		NSLog('读取1.txt失败:\n   ' + error)
	})

效果如下:

下面是Promise的精华部分了,请看演示demo  node_44.js代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 回调地狱的解决方案演示 Promise
var fs = require('fs')
// 自己封装一个readFile方法,参数是: filePath
// 返回值是: 一个promise
function beyondReadFile (filePath) {
	// 在ECMAScript6中,新增了一个API Promise
	// Promise是一个构造函数,目的就是避免回调地狱 callback hell
	// 通过new + 构造函数 创建一个Promise
	// 构造函数的参数 是一个匿名函数(block代码块)
	// Promise一旦创建,就会立即执行参数(即匿名函数block)里面的代码
	// 匿名函数block有两个参数是用于回调的,它们分别是resolveCallBack和rejectCallback
	// 对应于then方法的参数1和参数2
	var promise_1 = new Promise(function (resolveCallback,rejectCallback) {
		// Promise本身是同步顺序执行的,但是它里面的异步操作是异步执行的
		fs.readFile(filePath,'utf8',function (error,successData) {
			if (error === null) {
				// 如果成功,则承诺容器中的异步任务成功了
				// 成功回调函数(即then方法的参数1)
				resolveCallback(successData)
			} else{
				// 如果失败,则承诺容器中的异步任务失败了
				// NSLog('读取失败' + error)
				// 失败回调函数(即then方法的参数2)
				rejectCallback(error)
			}
		})
	})
	// 返回promise,目的是为了能够链式使用then语法
	return promise_1
}

// 调用一下,因为返回值是promise,所以可直接then
beyondReadFile('node_40_1.txt')
	.then(function (successData1) {
		NSLog('读取1.txt成功:\n   ' + successData1,false)
		// 下面是Promise最精彩的部分
		var promise_2 = beyondReadFile('node_40_2.txt')
		return promise_2
		/*
		关于这个返回值 要说明一下
		这个返回值类型 只有两种情况
		第1种情况: 返回一个promise对象
				  那么下一个then中,参数1就是promise对象的resolveCallback
				  参数2就是promise对象的rejectCallback
		第2种情况: 返回一个非Promise的对象 (极少用到)	
				  比如返回一个String字符串	 "那朵花"
				  那么,在下一个then中就只有一个匿名函数callback作为参数
				  那个匿名函数callback中的参数 就是这儿的返回值 即String的值  "那朵花"	
		*/
	},function (error1) {
		NSLog('读取1.txt失败:\n   ' + error1)
	})
	// 因为前面返回的是Promsise_2,所以可以接着then
	.then(function (successData2) {
		NSLog('读取2.txt成功:\n   ' + successData2)
	},function (error2) {
		NSLog('读取2.txt失败:\n   ' + error2)
	})

效果如下:

依此类推,下面的node_45.js演示的是读取并按顺序输出3个文件,代码如下:

function NSLog(loli,needLogo=true) {console.log(loli);if(needLogo){console.log('\nCopyright © 2018 Powered by beyond')};}  

// 回调地狱的解决方案演示 Promise
var fs = require('fs')
// 自己封装一个readFile方法,参数是: filePath
// 返回值是: 一个promise
function beyondReadFile (filePath) {
	// 在ECMAScript6中,新增了一个API Promise
	// Promise是一个构造函数,目的就是避免回调地狱 callback hell
	// 通过new + 构造函数 创建一个Promise
	// 构造函数的参数 是一个匿名函数(block代码块)
	// Promise一旦创建,就会立即执行参数(即匿名函数block)里面的代码
	// 匿名函数block有两个参数是用于回调的,它们分别是resolveCallBack和rejectCallback
	// 对应于then方法的参数1和参数2
	var promise_1 = new Promise(function (resolveCallback,rejectCallback) {
		// Promise本身是同步顺序执行的,但是它里面的异步操作是异步执行的
		fs.readFile(filePath,'utf8',function (error,successData) {
			if (error === null) {
				// 如果成功,则承诺容器中的异步任务成功了
				// 成功回调函数(即then方法的参数1)
				resolveCallback(successData)
			} else{
				// 如果失败,则承诺容器中的异步任务失败了
				// NSLog('读取失败' + error)
				// 失败回调函数(即then方法的参数2)
				rejectCallback(error)
			}
		})
	})
	// 返回promise,目的是为了能够链式使用then语法
	return promise_1
}

// 调用一下,因为返回值是promise,所以可直接then
beyondReadFile('node_40_1.txt')
	.then(function (successData1) {
		NSLog('读取1.txt成功:\n   ' + successData1,false)
		// 下面是Promise最精彩的部分
		var promise_2 = beyondReadFile('node_40_2.txt')
		return promise_2
		/*
		关于这个返回值 要说明一下
		这个返回值类型 只有两种情况
		第1种情况: 返回一个promise对象
				  那么下一个then中,参数1就是promise对象的resolveCallback
				  参数2就是promise对象的rejectCallback
		第2种情况: 返回一个非Promise的对象 (极少用到)	
				  比如返回一个String字符串	 "那朵花"
				  那么,在下一个then中就只有一个匿名函数callback作为参数
				  那个匿名函数callback中的参数 就是这儿的返回值 即String的值  "那朵花"	
		*/
	},function (error1) {
		NSLog('读取1.txt失败:\n   ' + error1)
	})
	// 因为前面返回的是Promsise_2,所以可以接着then
	.then(function (successData2) {
		NSLog('读取2.txt成功:\n   ' + successData2,false)
		// 下面是Promise最精彩的部分
		var promise_3 = beyondReadFile('node_40_3.txt')
		return promise_3
		/*
		关于这个返回值 要说明一下
		这个返回值类型 只有两种情况
		第1种情况: 返回一个promise对象
				  那么下一个then中,参数1就是promise对象的resolveCallback
				  参数2就是promise对象的rejectCallback
		第2种情况: 返回一个非Promise的对象 (极少用到)	
				  比如返回一个String字符串	 "那朵花"
				  那么,在下一个then中就只有一个匿名函数callback作为参数
				  那个匿名函数callback中的参数 就是这儿的返回值 即String的值  "那朵花"	
		*/
	},function (error2) {
		NSLog('读取2.txt失败:\n   ' + error2)
	})
	// 因为前面返回的是Promsise_2,所以可以接着then
	.then(function (successData3) {
		NSLog('读取3.txt成功:\n   ' + successData3)
	},function (error3) {
		NSLog('读取3.txt失败:\n   ' + error3)
	})

效果如下:

整个效果如下:

解释说明如下:

下面演示一个Promise使用场景的示例demo,

在这个demo中,教程里面使用了json-server来提供api接口(因此,我们先来安装和使用json-server)

下面,我们先根据npmjs.com上面的json-server的官方文档,安装和使用json-server

npmjs.com/package/json-server

第1步, 安装命令如下:

sudo npm install –global json-server

效果如下:

第2步, 创建597_db.json,内容如下:

注意: 主键属性名必须叫id

{
	"girls" : [
		{
			"id" : 1,
			"girlName" : "面码",
			"girlAge" : 13,
			"animeId" : 1
		},
		{
			"id" : 2,
			"girlName" : "mathilda",
			"girlAge" : 12,
			"animeId" : 2
		},
		{
			"id" : 3,
			"girlName" : "逢坂大河",
			"girlAge" : 16,
			"animeId" : 3
		},
		{
			"id" : 4,
			"girlName" : "平泽唯",
			"girlAge" : 14,
			"animeId" : 4
		}
	],
	"animes" : [
		{
			"id" : 1, 
			"animeName" : "未闻花名"
		},
		{
			"id" : 2,
			"animeName" : "这个杀手不太冷"
		},
		{
			"id" : 3,
			"animeName" : "龙与虎"
		},
		{
			"id" : 4,
			"animeName" : "轻音少女"
		}
	]
}

第3步, cd json所在目录,然后执行下面命令

json-server –watch 597_girls.json

效果如下:

第4步, 在地址栏输入 localhost:3000/girls   回车,请求接口时,返回的json结果如下:

输入localhost:3000/girls/1 回车,请求接口时,返回的json效果如下:

输入localhost:3000/animes 回车,请求接口时,返回的json效果如下:

教程中接下来又使用了一个命令

这个hs -c-l -o命令又是一个什么东东???

老规矩,先来安装一下 http-server

第1步, 使用以下命令

sudo npm install –global http-server

效果如下:

第2步, 启动http-server,输入

hs -c-l -o

效果如下:

官方说明如下: (意思是如果根目录下面没有public目录,就显示根目录文件列表)

http-server [path] [options]
[path] defaults to ./public if the folder exists, and ./ otherwise.

Now you can visit http://localhost:8080 to view your server

接下来安装模板引擎art-template

命令如下:

npm install art-template

效果如下:

接下来在597.html中使用模板引擎template-web.js

注意: json服务器的接口是 localhost:3000/girls/1

而http-server提供的服务器是 localhost:8080/597.html

我们在597.html中,使用596.html中自己封装的beyondGet方法,发送一个ajax请求 json服务器上的数据,

并且将返回的json字符串解析成对象,通过模板引擎,填充到form表单列表中

现在我们的597.html到底是要实现什么样的场景呢?

那就是: 发两次异步请求

第1次异步请求回girl的信息,其中girl对象中有一个属性animeId,记录她出演的动漫id

第2次异步请求回动漫列表

等两次请求相继完成后,我们再进行页面的渲染,并且在渲染的时候,会判断

如果girl对象的animeId属性  跟下拉的动漫列表 框中的id一致时,我们就让这个option默认为选中状态

下面是不用Promise的写法:

597.html代码如下:

<!DOCTPYE html>  
<html lang="zh">  
<head>  
    <link rel="icon" href="beyond2.jpg" type="image/x-icon"/>
    <meta charset="UTF-8">
    <meta name="author" content="beyond">
    <meta http-equiv="refresh" content="520">
    <meta name="description" content="未闻花名-免费零基础教程-beyond">
    <meta name="viewport" content="width=device-width, 
    initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
    <meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,汇编,日语,英语,泰语,韩语,俄语,粤语,阿语,魔方,乐理,动漫,PR,PS,AI,AE">
    <title>beyond心中の动漫神作</title>
    <link rel="stylesheet" type="text/css" href="beyondbasestylewhite5.css">
    <script type="text/javascript" src="nslog.js"></script>

    <!--[if lt IE 9]>
        <script src="//apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
        <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.js">
        </script>
    <![endif]-->

    <style type="text/css">
        body{
            font-size: 100%; 
            /*声明margin和padding是个好习惯*/  
            margin: 0;  
            padding: 0; 
            background-image: url("sakura4.png");  
            background-repeat: no-repeat;  
            background-position: center center;  
        }
    </style>
    <!-- 绿色按钮的css效果 -->
    <link rel="stylesheet" type="text/css" href="beyondbuttongreen.css">

    <!-- 引入 jquery 2.1.4 -->
    <!--[if gte IE 9]><!--> 
    <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js">
    </script>
    <!--<![endif]-->


    
</head>  
  
<body>  
        <h1 style="color:white;text-shadow:2px 2px 4px #000;letter-spacing:5px;" class="sgcontentcolor sgcenter">  
            未闻花名
        </h1>

        <!-- 渲染结果显示区域 -->
        <form action="" id="id_form_renderResult" style="text-align:center;" >
        </form>


        <!-- 第1步,导入template-web.js -->
<script type="text/javascript" src="node_modules/art-template/lib/template-web.js"></script>


        <!-- 第2步,定义模板引擎 -->  
        <!-- type只要 不是javascript即可,随便起,无意义   
             id 在第3步填充数据时用到  
        -->  
<script type="text/beyondtemplate" id="art_template">  
    <div >
        <label for="">芳名</label>    
        <input type="text" value="{{ girl.girlName }}"/>
    </div>

    <div >
        <label for="">年龄</label>    
        <input type="text" value="{{ girl.girlAge }}"/>
    </div>

    <div>
        <label for="">主演动漫</label>
        <select>
            {{ each animes }}
                {{ if girl.animeId === $value.id }}
                    <option value="{{ $value.id }}" selected> {{ $value.animeName }} </option>
                {{ else }}
                    <option value="{{ $value.id }}"> {{ $value.animeName }} </option>
                {{ /if }}
            {{ /each }}
        </select>
    </div>
</script>        

    
        <button id="id_btn" class="class_btn class_btn_green" type="button" style="display:block;margin:auto;margin-top:10px;">点我试试</button>
        <script type="text/javascript">
            // 自己封装一个get请求  
            function beyondGet (url,callback) {  
                   // 自己实现一个ajax请求  
                   // 1. 创建xhr  
                   var xmlHttpRequest = new XMLHttpRequest()  
                   // 2. 请求url资源  
                   xmlHttpRequest.onload = function () {  
                      // 5. 核心代码,回调结果  
                      callback(xmlHttpRequest.responseText)
                   }  
                   // 3.连接资源  
                   xmlHttpRequest.open("get",url,true)  
                   // 4.发送请求  
                   xmlHttpRequest.send()  
            }  

            $(document).ready(function () {  
               $(".class_btn_green").click(function () {  
                  // 使用自己封装的ajax请求  
                  // 第1个接口, 获取girl的数据
                  beyondGet("http://127.0.0.1:3000/girls/2",function (responseGirl) {  
                    
                    var girlObj = JSON.parse(responseGirl)
                    // 第2个接口,获取所有的动漫列表接口
                    beyondGet('http://127.0.0.1:3000/animes',function (responseAnimes) {
                        var animeArr = JSON.parse(responseAnimes)
                        // <!-- 第3步,给模板引擎绑定数据 -->  
                        // 注意: 函数名,必须是template  
                        // 参数1: 模板的id  
                        // 参数2: 要填充的数据封装成的 对象  
                        var resultHTML = template('art_template',{
                            girl: girlObj,
                            animes: animeArr
                        })
                        // 这是干嘛:将渲染后的字符串,显示到界面上
                        document.querySelector('#id_form_renderResult').innerHTML = resultHTML
                        NSLog('resultHTML: \n' + resultHTML)
                    })
                  })  
                     
               })  
            })
        </script>

        <p class="sgcenter">
          <b>注意:</b>模拟Promise使用场景<br/>
          json-server提供API服务(3000端口)<br/>
          http-server提供Web服务(8080端口)<br/>
          封装了XMLHttpRequest.onload方法<br/>
          art-template的template-web.js渲染
        </p>

        <footer id="copyright">
            <p style="font-size:14px;text-align:center;font-style:italic;">  
            Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>  
            </p>        
        </footer>
    
</body>  
</html>  

效果如下:

下面的598.html中使用的是jQuery的get方法(支持Promise),代码如下:

注意$.get(urlPath)只有一个参数时,默认返回的是一个Promise

<!DOCTPYE html>  
<html lang="zh">  
<head>  
    <link rel="icon" href="beyond2.jpg" type="image/x-icon"/>
    <meta charset="UTF-8">
    <meta name="author" content="beyond">
    <meta http-equiv="refresh" content="520">
    <meta name="description" content="未闻花名-免费零基础教程-beyond">
    <meta name="viewport" content="width=device-width, 
    initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
    <meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,汇编,日语,英语,泰语,韩语,俄语,粤语,阿语,魔方,乐理,动漫,PR,PS,AI,AE">
    <title>beyond心中の动漫神作</title>
    <link rel="stylesheet" type="text/css" href="beyondbasestylewhite5.css">
    <script type="text/javascript" src="nslog.js"></script>

    <!--[if lt IE 9]>
        <script src="//apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
        <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.js">
        </script>
    <![endif]-->

    <style type="text/css">
        body{
            font-size: 100%; 
            /*声明margin和padding是个好习惯*/  
            margin: 0;  
            padding: 0; 
            background-image: url("sakura4.png");  
            background-repeat: no-repeat;  
            background-position: center center;  
        }
    </style>
    <!-- 绿色按钮的css效果 -->
    <link rel="stylesheet" type="text/css" href="beyondbuttongreen.css">

    <!-- 引入 jquery 2.1.4 -->
    <!--[if gte IE 9]><!--> 
    <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js">
    </script>
    <!--<![endif]-->


    
</head>  
  
<body>  
        <h1 style="color:white;text-shadow:2px 2px 4px #000;letter-spacing:5px;" class="sgcontentcolor sgcenter">  
            未闻花名
        </h1>

        <!-- 渲染结果显示区域 -->
        <form action="" id="id_form_renderResult" style="text-align:center;" >
        </form>


        <!-- 第1步,导入template-web.js -->
<script type="text/javascript" src="node_modules/art-template/lib/template-web.js"></script>


        <!-- 第2步,定义模板引擎 -->  
        <!-- type只要 不是javascript即可,随便起,无意义   
             id 在第3步填充数据时用到  
        -->  
<script type="text/beyondtemplate" id="art_template">  
    <div >
        <label for="">芳名</label>    
        <input type="text" value="{{ girl.girlName }}"/>
    </div>

    <div >
        <label for="">年龄</label>    
        <input type="text" value="{{ girl.girlAge }}"/>
    </div>

    <div>
        <label for="">主演动漫</label>
        <select>
            {{ each animes }}
                {{ if girl.animeId === $value.id }}
                    <option value="{{ $value.id }}" selected> {{ $value.animeName }} </option>
                {{ else }}
                    <option value="{{ $value.id }}"> {{ $value.animeName }} </option>
                {{ /if }}
            {{ /each }}
        </select>
    </div>
</script>        

    
        <button id="id_btn" class="class_btn class_btn_green" type="button" style="display:block;margin:auto;margin-top:10px;">点我试试</button>
        <script type="text/javascript">
            var girlObj = null 
            $(document).ready(function () {  
               $(".class_btn_green").click(function () {  
                  // 使用自己封装的ajax请求  
                  // 第1个接口, 获取girl的数据
                  // get方法只有一个参数时,返回的就是一个Promise
                  $.get("http://127.0.0.1:3000/girls/2")
                        .then(function (responseGirlObj) {  
                            // 因为第2个接口中拿不到,所以使用全局变量
                            girlObj = responseGirlObj
                            // 返回一个Promise
                            // 第2个接口,获取所有的动漫列表接口
                            $.get('http://127.0.0.1:3000/animes')
                        })
                      .then(function (responseAnimes) {
                            var animeArr = responseAnimes
                            // <!-- 第3步,给模板引擎绑定数据 -->  
                            // 注意: 函数名,必须是template  
                            // 参数1: 模板的id  
                            // 参数2: 要填充的数据封装成的 对象  
                            var resultHTML = template('art_template',{
                                girl: girlObj,
                                animes: animeArr
                            })
                            // 这是干嘛:将渲染后的字符串,显示到界面上
                            document.querySelector('#id_form_renderResult').innerHTML = resultHTML
                            NSLog('resultHTML: \n' + resultHTML)
                        })
                  
                     
               })  
            })
        </script>

        <p class="sgcenter">
          <b>注意:</b>模拟Promise使用场景<br/>
          json-server提供API服务(3000端口)<br/>
          http-server提供Web服务(8080端口)<br/>
          jQuery的get方法 默认 支持Promise<br/>
          art-template的template-web.js渲染
        </p>

        <footer id="copyright">
            <p style="font-size:14px;text-align:center;font-style:italic;">  
            Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>  
            </p>        
        </footer>
    
</body>  
</html>  

效果如下:

下面的599.html中使用的是自己封装的Get + Promise方法,代码如下:

<!DOCTPYE html>  
<html lang="zh">  
<head>  
    <link rel="icon" href="beyond2.jpg" type="image/x-icon"/>
    <meta charset="UTF-8">
    <meta name="author" content="beyond">
    <meta http-equiv="refresh" content="520">
    <meta name="description" content="未闻花名-免费零基础教程-beyond">
    <meta name="viewport" content="width=device-width, 
    initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
    <meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,汇编,日语,英语,泰语,韩语,俄语,粤语,阿语,魔方,乐理,动漫,PR,PS,AI,AE">
    <title>beyond心中の动漫神作</title>
    <link rel="stylesheet" type="text/css" href="beyondbasestylewhite5.css">
    <script type="text/javascript" src="nslog.js"></script>

    <!--[if lt IE 9]>
        <script src="//apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
        <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.js">
        </script>
    <![endif]-->

    <style type="text/css">
        body{
            font-size: 100%; 
            /*声明margin和padding是个好习惯*/  
            margin: 0;  
            padding: 0; 
            background-image: url("sakura4.png");  
            background-repeat: no-repeat;  
            background-position: center center;  
        }
    </style>
    <!-- 绿色按钮的css效果 -->
    <link rel="stylesheet" type="text/css" href="beyondbuttongreen.css">

    <!-- 引入 jquery 2.1.4 -->
    <!--[if gte IE 9]><!--> 
    <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js">
    </script>
    <!--<![endif]-->


    
</head>  
  
<body>  
        <h1 style="color:white;text-shadow:2px 2px 4px #000;letter-spacing:5px;" class="sgcontentcolor sgcenter">  
            未闻花名
        </h1>

        <!-- 渲染结果显示区域 -->
        <form action="" id="id_form_renderResult" style="text-align:center;" >
        </form>


        <!-- 第1步,导入template-web.js -->
<script type="text/javascript" src="node_modules/art-template/lib/template-web.js"></script>


        <!-- 第2步,定义模板引擎 -->  
        <!-- type只要 不是javascript即可,随便起,无意义   
             id 在第3步填充数据时用到  
        -->  
<script type="text/beyondtemplate" id="art_template">  
    <div >
        <label for="">芳名</label>    
        <input type="text" value="{{ girl.girlName }}"/>
    </div>

    <div >
        <label for="">年龄</label>    
        <input type="text" value="{{ girl.girlAge }}"/>
    </div>

    <div>
        <label for="">主演动漫</label>
        <select>
            {{ each animes }}
                {{ if girl.animeId === $value.id }}
                    <option value="{{ $value.id }}" selected> {{ $value.animeName }} </option>
                {{ else }}
                    <option value="{{ $value.id }}"> {{ $value.animeName }} </option>
                {{ /if }}
            {{ /each }}
        </select>
    </div>
</script>        

    
        <button id="id_btn" class="class_btn class_btn_green" type="button" style="display:block;margin:auto;margin-top:10px;">点我试试</button>
        <script type="text/javascript">
            // 自己封装一个get请求 (返回Promise)
            function beyondGet (url,callBack) {  
                var promise = new Promise(function (resolveCallback,rejectCallback) {
                    // 异步请求
                    // 自己实现一个ajax请求  
                    // 1. 创建xhr  
                    var xmlHttpRequest = new XMLHttpRequest()  
                    // 2. 请求url资源  
                    xmlHttpRequest.onload = function () {  
                       // 5. 核心代码,回调结果  
                       // 既可以在参数中传入callBack,使用回调模式,又可以不传而使用.then形式
                       // 当用户不传callBack时,使用.then形式
                       callBack && callBack(xmlHttpRequest.responseText)
                       resolveCallback(JSON.parse(xmlHttpRequest.responseText))
                    }  
                    xmlHttpRequest.onerror = function (error) {
                        // 5.核心代码,回调结果
                        rejectCallback(error)
                    }
                    // 3.连接资源  
                    xmlHttpRequest.open("get",url,true)  
                    // 4.发送请求  
                    xmlHttpRequest.send() 
                })
                // 核心代码,返回promise
                return promise
            } 

            var girlObj = null 
            $(document).ready(function () {  
               $(".class_btn_green").click(function () {  
                  // 使用自己封装的ajax请求  
                  // 第1个接口, 获取girl的数据
                  // 自定义的get方法 返回的就是一个Promise
                  beyondGet("http://127.0.0.1:3000/girls/2")
                        .then(function (responseGirlObj) {
                            // 因为第2个接口中拿不到,所以使用全局变量
                            girlObj = responseGirlObj
                            // 返回一个Promise
                            // 第2个接口,获取所有的动漫列表接口
                            var promise_2 = beyondGet('http://127.0.0.1:3000/animes')
                            return promise_2
                        })
                      .then(function (responseAnimes) {
                            var animeArr = responseAnimes
                            // <!-- 第3步,给模板引擎绑定数据 -->  
                            // 注意: 函数名,必须是template  
                            // 参数1: 模板的id  
                            // 参数2: 要填充的数据封装成的 对象  
                            var resultHTML = template('art_template',{
                                girl: girlObj,
                                animes: animeArr
                            })
                            // 这是干嘛:将渲染后的字符串,显示到界面上
                            document.querySelector('#id_form_renderResult').innerHTML = resultHTML
                            NSLog('resultHTML: \n' + resultHTML)
                        })
                  
                     
               })  
            })
        </script>

        <p class="sgcenter">
          <b>注意:</b>模拟Promise使用场景<br/>
          json-server提供API服务(3000端口)<br/>
          http-server提供Web服务(8080端口)<br/>
          自己封装Get方法 (return Promise)<br/>
          既可以在Get参数中 传入callBack形式<br/>
          又可不传callBack参数,使用.then形式<br/>
          art-template的template-web.js渲染
        </p>

        <footer id="copyright">
            <p style="font-size:14px;text-align:center;font-style:italic;">  
            Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>  
            </p>        
        </footer>
    
</body>  
</html>  

效果如下:

最后,我们把前面的mongoose案例node_36_router.js这个文件

全部改写成return Promise 加 .then调用形式

(另外的node_36_index.js和node_36_dao.js以及3个html文件 几乎都不用动)

改写后的代码node_47_router.js代码如下:

function NSLog(loli) {console.log(loli);return 'Copyright © 2018 Powered by beyond';};  
/*
	自定义路由模块的职责是:
		专门处理所有的路由
		根据不同的请求方式和路径,采取相应的处理方法
*/ 
// express 专门提供了路由的处理方法
var express = require('express')
// 1.使用express专门提供的路由器处理路由
var router = express.Router()

// ----------------引入dao模块-------------------
// 先对dao初始化
var GirlDaoFunction = require('./node_47_dao')
// 时间格式化
var BeyondDateFormatFunction = require('./BeyondDateFormat')

// ----------------首页-------------------
router.get('/',function (request,response) {
	// 至于请求参数可以这样:
	// var queryObj = request.query

	// ----------------查找所有------------------
	// 查找所有,第1个参数不写,就是查找所有
	GirlDaoFunction.find()
		// find 方法返回的是:promise
		// promise的then方法的参数1是 resolveCallback
		// promise的then方法的参数2是 rejectCallback
		.then(function (girls) {
			// then方法的参数1是: resolveCallback
			
			// 使用模板引擎渲染
			// 注意: 模板文件默认是放在views目录下
			// 为此,我们在views目录下  分别为不同的业务模块创建了不同的文件夹
			// 如 login登录 admin后台管理 index前台首页 article文章 comment评论
			response.render('index/node_47_index.html',{girlArr:girls})
		},function (error) {
			// then方法的参数2是: rejectCallback
			if (error) {
				return NSLog('查询出错: ' + error)
			}
		})

})

// ----------------添加的表单页面-------------------
router.get('/add',function (request,response) {
	response.render('index/node_47_add.html')
})


// ----------------增加一条记录-------------------
router.post('/insert',function (request,response) {
	// 1.body-parser得到obj
	var girlObj = request.body
	// NSLog(girlObj)
	// 1.调用girlDao写到文件数据库
	girlObj.pubTime = BeyondDateFormatFunction(new Date(),'yyyy-MM-dd')
	var newGirl = new GirlDaoFunction(girlObj)
	// 2.保存到数据库
	newGirl.save()
		// save 方法返回的是:promise
		// promise的then方法的参数1是 resolveCallback
		// promise的then方法的参数2是 rejectCallback
		.then(function () {
			// then方法的参数1是: resolveCallback
			// 没有错误,跳转到首页	
			response.redirect('/')
		},function(error) {
			// then方法的参数2是: rejectCallback
			if (error) {
			// 有错误
			return response.status(500).send(error)
		}
		})
})

// ----------------修改页面------------------- 
router.get('/edit',function (request,response) {
	// 查询的girlID 不知怎么滴,首尾有引号
	//  "5ad42675f917fa32e250a58a"
	// 使用正则,把引号去掉, 如果不使用g的话,仅仅只是去掉第1个引号
	var pureId = request.query._id.replace(/"/g,"")
	// NSLog('query: ' + pureId)
	GirlDaoFunction.findById(pureId)
		// findById 方法返回的是:promise
		// promise的then方法的参数1是 resolveCallback
		// promise的then方法的参数2是 rejectCallback
		.then(function (girl) {
			// then方法的参数1是: resolveCallback
			response.render('index/node_47_edit.html',{'girl': girl})
		},function (error) {
			// then方法的参数2是: rejectCallback
			if (error) {
				return response.status(500),send(error)
			}
		})
})
// ----------------更新数据库------------------- 
router.post('/update',function (request,response) {
	// 1.请求体 id号 (前后多了两个引号,要手动去掉)
	var girlID = request.body._id
	var girlIDWithoutQuote = girlID.replace(/"/g,'')
	// 2.重新设置回去
	request.body._id = girlIDWithoutQuote
	// 3.根据id查询和更新
	GirlDaoFunction.findByIdAndUpdate(girlIDWithoutQuote,request.body)
		// findByIdAndUpdate 方法返回的是:promise
		// promise的then方法的参数1是 resolveCallback
		// promise的then方法的参数2是 rejectCallback
		.then(function () {
			// then方法的参数1是: resolveCallback
			// 如果保存成功,回首页
			response.redirect('/')
		},function() {
			// then方法的参数2是: rejectCallback
			if(error){
				// 如果保存出错了
				return response.status(500).send(error)
			}
		})
})

// ----------------删除一条记录------------------- 
router.get('/delete',function (request,response) {
	// 1.获取query对象中的_id
	var girlID = request.query._id
	var girlIDWithoutQuote = girlID.replace(/"/g,'')
	// 2.调用dao从数据库中删除一个对象
	GirlDaoFunction.findByIdAndRemove(girlIDWithoutQuote)
		// findByIdAndRemove 方法返回的是:promise
		// promise的then方法的参数1是 resolveCallback
		// promise的then方法的参数2是 rejectCallback
		.then(function () {
			// then方法的参数1是: resolveCallback
			// 如果没有错误,跳转到首页
			return response.redirect('/')
		},function(error) {
			// then方法的参数2是: rejectCallback
			if (error) {
				// 有错误
				return response.status(500).send(error)
			}
		})
})


// 3.在模块文件最后,导出router
module.exports = router

效果如下: (跟node_36_router.js效果是一样的)

附上原来的使用callback回调函数作参数的写法的node_36_router.js对比一下

function NSLog(loli) {console.log(loli);return 'Copyright © 2018 Powered by beyond';};  
/*
	自定义路由模块的职责是:
		专门处理所有的路由
		根据不同的请求方式和路径,采取相应的处理方法
*/ 
// express 专门提供了路由的处理方法
var express = require('express')
// 1.使用express专门提供的路由器处理路由
var router = express.Router()

// ----------------引入dao模块-------------------
// 先对dao初始化
var GirlDaoFunction = require('./node_36_dao')
// 时间格式化
var BeyondDateFormatFunction = require('./BeyondDateFormat')

// ----------------首页-------------------
router.get('/',function (request,response) {
	// 至于请求参数可以这样:
	// var queryObj = request.query

	// ----------------查找所有------------------
	// 查找所有,第1个参数不写,就是查找所有
	GirlDaoFunction.find(function (error,girls) {
	if (error) {
		return NSLog('查询出错: ' + error)
	}

	// 使用模板引擎渲染
		// 注意: 模板文件默认是放在views目录下
		// 为此,我们在views目录下  分别为不同的业务模块创建了不同的文件夹
		// 如 login登录 admin后台管理 index前台首页 article文章 comment评论
		response.render('index/node_36_index.html',{girlArr:girls})

	})

})

// ----------------添加的表单页面-------------------
router.get('/add',function (request,response) {
	response.render('index/node_36_add.html')
})


// ----------------增加一条记录-------------------
router.post('/insert',function (request,response) {
	// 1.body-parser得到obj
	var girlObj = request.body
	// NSLog(girlObj)
	// 1.调用girlDao写到文件数据库
	girlObj.pubTime = BeyondDateFormatFunction(new Date(),'yyyy-MM-dd')
	var newGirl = new GirlDaoFunction(girlObj)
	// 2.保存到数据库
	newGirl.save(function (error) {
		if (error) {
			// 有错误
			return response.status(500).send(error)
		}
		// 没有错误,跳转到首页	
		response.redirect('/')
	})
})

// ----------------修改页面------------------- 
router.get('/edit',function (request,response) {
	// 查询的girlID 不知怎么滴,首尾有引号
	//  "5ad42675f917fa32e250a58a"
	// 使用正则,把引号去掉, 如果不使用g的话,仅仅只是去掉第1个引号
	var pureId = request.query._id.replace(/"/g,"")
	// NSLog('query: ' + pureId)
	GirlDaoFunction.findById(pureId,function (error,girl) {
		if (error) {
			return response.status(500),send(error)
		}
		response.render('index/node_36_edit.html',{'girl': girl})
	})
})
// ----------------更新数据库------------------- 
router.post('/update',function (request,response) {
	// 1.请求体 id号 (前后多了两个引号,要手动去掉)
	var girlID = request.body._id
	var girlIDWithoutQuote = girlID.replace(/"/g,'')
	// 2.重新设置回去
	request.body._id = girlIDWithoutQuote
	// 3.根据id查询和更新
	GirlDaoFunction.findByIdAndUpdate(girlIDWithoutQuote,request.body,function (error) {
		if(error){
			// 如果保存出错了
			return response.status(500).send(error)
		}
		// 如果保存成功,回首页
		response.redirect('/')
	})
})

// ----------------删除一条记录------------------- 
router.get('/delete',function (request,response) {
	// 1.获取query对象中的_id
	var girlID = request.query._id
	var girlIDWithoutQuote = girlID.replace(/"/g,'')
	// 2.调用dao从数据库中删除一个对象
	GirlDaoFunction.findByIdAndRemove(girlIDWithoutQuote,function (error) {
		if (error) {
			// 有错误
			return response.status(500).send(error)
		}
		// 如果没有错误,跳转到首页
		return response.redirect('/')
	})
})


// 3.在模块文件最后,导出router
module.exports = router

未完待续,下一章节,つづく