得到函数内部异步操作的结果

回调函数:通过一个函数,获取函数内部的操作。(根据输入得到输出结果)

  • 在该情况下无法获得函数内异步操作的结果
1
2
3
4
5
6
7
8
9
10
11
function get(a, b) {
console.log(a + b)
setTimeout(function() {
console.log(2)
ret = a + b
return ret
}, 6000);
console.log(3)
}

console.log(get(20,30))

结果:返回undefined

image-20200524151737456

  • 若要获得该数据则只能通过回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function get(a, b, c) {
console.log(a + b)
setTimeout(function() {
console.log(2)
ret = a + b
return c(ret)
}, 6000);
console.log(3)
}

// console.log(get(20,30))

get(20, 30, function(a) {
console.log(a)
})

结果

image-20200524152155139

  • 注意:

    凡是需要得到一个函数内部异步操作的结果(setTimeout,readFile,writeFile,ajax,readdir)

    这种情况必须通过 回调函数 (异步API都会伴随着一个回调函数)

回调地狱

啥是回调地狱

就是这幅图(图源网络)

image-20200527211637374

为什么会有回调地狱

回调地狱的原因是,当人们试图以一种从上到下的视觉方式执行JavaScript的方式编写JavaScript时。期望第1行发生的任何事情都会在第2行的代码开始运行之前完成,但是,在JavaScript上,有时候这并没办法进行,比如,在你通过异步读取文件时,也就是用fs模块读取多个文件时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var fs = require('fs')

fs.readFile('./a.txt', 'utf8', function(err, data) {
if (err) {
console.log(err)
}
console.log(data)
})

fs.readFile('./b.txt', 'utf8', function(err, data) {
if (err) {
console.log(err)
}
console.log(data)
})

fs.readFile('./c.txt', 'utf8', function(err, data) {
if (err) {
console.log(err)
}
console.log(data)
})

执行多次后,你会发现,有那么几次,也有可能好几次,看人品吧反正是,它是没有规则的读出来的(往往可能你的文件越大,读出来的时间会更久),也就是说,它并不会按照代码书写顺序去执行,这便是异步编程(如果试了没有,那就一直试,反正总会有的)。异步API导致了代码并不是按顺序执行的(可以读读这篇文章 https://www.jianshu.com/p/39adf6ab8ad1 ——然后嘞,就会有上面那种解决方法,但是你会发现,代码非常的丑(别人是这样说的,反正我不是太这么认为,甚至觉得有点好看),还有非常难维护(这点认同)。所以就出现了几种解决方法 —Promise

Promise

简介

  • Promise:承诺,保证
  • Promise本身不是异步的,但往往都是内部封装一个异步任务

丢出一张图形容Promise函数,相当于一个容器(下图源于所学教程,pending(悬而未决的))

image-20200527213850773

代码如下,较易维护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var fs = require('fs')

//resolved(解决(成功)),rejected(驳回(失败))
var p1 = new Promise(function(resolved, rejected) {
//文件编码!!!!!!
fs.readFile('./a.txt', 'utf8', function(err, data) {
if (err) {
rejected(err)
}
resolved(data)
})
})

var p2 = new Promise(function(resolved, rejected) {
fs.readFile('./b.txt', 'utf8', function(err, data) {
if (err) {
rejected(err)
}
resolved(data)
})
})

var p3 = new Promise(function(resolved, rejected) {
fs.readFile('c.txt', function(err, data) {
if (err) {
rejected(err)
}
resolved(data)
})
})

//链式编程,🔺Promise会默认将then中return的值实例成一个promise对象,所以可以调用then方法,实现链式调用
p1
.then(function(data) {
console.log(data)
return p2
})
.then(function(data) {
console.log(data)
return p3
}).then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})

then函数(ES6)说明:

image-20200527214049975

封装Promise中的readFile方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var fs = require('fs')

var pReadFile = function(filepath) {
return new Promise(function(resolved, rejected) {
//文件编码!!!!!!
fs.readFile(filepath, 'utf8', function(err, data) {
if (err) {
rejected(err)
}
resolved(data)
})
})
}

pReadFile("a.txt")
.then(function(data) {
console.log(data)
return pReadFile("b.txt")
})
.then(function(data) {
console.log(data)
return pReadFile("c.txt")
}).then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})

Promise应用场景

解决客户端回调嵌套问题

当出现类似于表关联的数据时,这时候就会遇到嵌套问题,当嵌套的数据只有一两个个还好,如果出现三四个甚至五六个,这时候就会出现回调地狱的问题,这里使用promise解决

所需知识:

  1. npm模块:json-server、http-server
  2. 客户端模板引擎art-template
  3. Ajax
  4. jquery

步骤

  1. 安装json-server和http-server以及其他必要模块

    1
    2
    3
    4
    npm i -g http-server
    npm i -g json-server
    npm i jquery --save
    npm i template --save
  2. 建立html页面

  3. 设计表单,人员信息与工作职业相关联,搭配模板字符串使用

    1
    <form action="" id="user_form"></form>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script type="text/template" id="tpl">
    <div>
    <label for="">用户名</label>
    <input type="text" value="{{ user.username }}">
    </div>
    <div>
    <label for="">年龄</label>
    <input type="text" value="{{ user.age }}">
    </div>
    <div>
    <label for="">职业</label>
    <select name="" id="">
    {{ each jobs }} {{ if user.job === $value.id }}
    <option value="{{ $value.id }}" selected>{{ $value.name }}</option>
    {{ else }}
    <option value="{{ $value.id }}">{{ $value.name }}</option>
    {{ /if }} {{ /each }}
    </select>
    </div>
    </script>
  4. 引用相关模板字符串以及JQuery模块

    1
    2
    <script src="node_modules/art-template/lib/template-web.js"></script>
    <script src="node_modules/jquery/dist/jquery.js"></script>
  5. 书写Ajax向服务器发起请求,并封装便于使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
        	function get(url, callback) {
    var oReq = new XMLHttpRequest()
    oReq.onload = function () {
    oReq.responseText
    callback(oReq.responseText)
    }
    oReq.open("get", url, true)
    oReq.send()
    }
  6. 开启json-server服务,使用data.json文件(6、7步使用cmd)

    1
    json-server data.json
  7. 将当前文件所处文件夹开放为服务器

    1
    http-server
    • 若要禁用缓存,则使用以下命令
    1
    http-server -c-1
  8. 🔺请求代码

    1. 若采用回调地狱类型

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      get("http://127.0.0.1:3000/users/1",function(userData){
      get("http://127.0.0.1:3000/jobs",function(jobsData){
      var htmlStr = template("tpl", {
      user: JSON.parse(userData),
      jobs: JSON.parse(jobsData)
      })
      console.log(htmlStr)
      document.querySelector("#user_form").innerHTML = htmlStr
      })
      })
    2. 使用Jquery版的Promise

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      //Jquery的Ajax自带promise
      var data = {}
      $.get("http://127.0.0.1:3000/users/2")
      .then(function(user){
      data.user = user
      return $.get("http://127.0.0.1:3000/jobs")
      })
      .then(function(jobs){
      data.jobs = jobs
      // console.log(data)
      var str = template("tpl",{
      user: data.user,
      jobs: data.jobs
      })
      document.querySelector('#user_form').innerHTML = str
      })
    3. 封装Promise版本的AJAX方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      function Rget(url,callback){
      return new Promise(function(resolve,reject){
      var xhr = new XMLHttpRequest()
      // 当请求加载成功之后要调用指定的函数
      xhr.onload = function () {
      // 我现在需要得到这里的 xhr.responseText
      resolve(JSON.parse(xhr.responseText))
      callback && callback(JSON.parse(xhr.responseText))
      }
      xhr.onerror = function (err){
      reject(err)
      }
      xhr.open("get", url, true)
      xhr.send()
      })
      }
      • 使用

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        var data = {}
        Rget("http://127.0.0.1:3000/users/2")
        .then(function(user){
        data.user = user
        return Rget("http://127.0.0.1:3000/jobs")
        })
        .then(function(jobs){
        data.jobs = jobs
        var str = template("tpl", {
        user: data.user,
        jobs: data.jobs
        })
        document.querySelector("#user_form").innerHTML = str
        })

        Promise操作数据库

  • mongoose中所有的API都支持promise

🔺根据查询是否已存在该记录从而决定是否创建新记录

1
2
3
4
5
6
7
8
9
10
11
Cat.findOne({ name: "好啊" })
.then(function(cat){
if(cat){
console.log('该cat已存在')
} else{
return new Cat({"name" : "好啊", "age" : 16 }).save()
}
})
.then(function(data){
console.log(data)
})

注意:

  1. 每次改完js或html文件后在浏览器需刷新多次
  2. 每次改完json文件后需要重新启动json-server服务

catch异常处理

在全部then之后添加.catch(err => {})即可对任何一个then处理过程抛出的异常进行捕获并中止代码继续执行

例如:读取文件并进行后续相关操作,若处理过程发生一个错误则传递给catch,则catch前面,出错误的then后面所有的then就不再执行

这里要注意区分,如果是在then中自行处理err,则代码还是会继续往下执行,这是和catch不同的点

还有如果在catch后面继续then,则还是会继续执行下去

1
2
3
4
5
6
7
8
9
10
11
readFile('a.txt', 'utf8')
.then(data => {
console.log(data)
return readFile('a.txt', 'utf8')
})
.then(data => {
console.log(data)
})
.catch(err => {
console.log(err)
})