目录
  1. 1. 前言
  2. 2. 回调函数
  3. 3. 现实需求
  4. 4. promise
    1. 4.1. Promise概念介绍
    2. 4.2. new一个promise实例
    3. 4.3. 创建一个具体的异步操作
    4. 4.4. promise读文件例子
    5. 4.5. 采用promise
promise篇

前言

要理解promise咱们从最基本的回调函数说起吧,然后再引入我们需要用回调函数来解决的现实需求,然而这种解决办法带来一些问题,最后讲promise如何改进了解决这种需求的方法的。

回调函数

初学者理解的难点,比如异步函数这些要想理解的容易一些,就需要想想为什么要弄个异步函数出来,顺序执行不好么。
这里举一个生活中的例子:比如你要为你的小侄子做一个生日贺卡,分两步完成,首先画一个小猪佩奇剪切下来,然后在贺卡上写下祝福语,再把小猪佩奇贴到贺卡上,假如你有一个帮手(异步处理队列),这里有两种操作方式选择:第一,让你的帮手画小猪佩奇,等待他画好之后,你开始写祝福语,然后再把贴画贴上去;第二,让你的帮手画佩奇,你同时开始写贺卡,写完贺卡问帮手要贴画(回调),然后贴在贺卡上。
很显然我们会选择第二种,从提高计算机处理性能的角度去想你会发现容易很多。

现实需求

文件夹files中有1.text、2.text、3.text这三个文件,里面分别写111、222、333,现在需要按顺序读取这三个text文件依次输出111 222 333.

首先封装一个读取文件的回调函数:

1
const fs = require('fs')
2
const path = require('path')
3
4
5
function getFileByPath(fpath, succCb, errCb) {
6
    fs.readFile(fpath, 'utf-8', (err, dataStr) => {
7
        if (err) return errCb(err)
8
        succCb(dataStr)
9
    })
10
}
11
//succCb为成功的回调、errCb为失败的回调

然后调用的时候为了保证按顺序读取以上三个文件,需要相互嵌套依次来调用,如下代码

1
getFileByPath(path.join(__dirname, './files/1.txt'), function(data) {
2
    console.log(data)
3
4
    getFileByPath(path.join(__dirname, './files/2.txt'), function(data) {
5
        console.log(data)
6
7
        getFileByPath(path.join(__dirname, './files/3.txt'), function(data) {
8
            console.log(data)
9
        })
10
    })
11
})

输出结果如图:

这种方式虽然满足需求,但当层级很多时,嵌套层数加深,不美观,不利于维护,后面的括号占用格子没有意义,我们称之为回调地狱。

所以使用 ES6 中的 Promise,来解决 回调地狱的问题;

promise

promise可以解决回调地狱,但是要注意Promise 的本质就是单纯的为了解决回调地狱问题,并不能帮我们减少代码量。

Promise概念介绍

  1. Promise 是一个 构造函数,既然是构造函数, 那么,我们就可以 new Promise() 得到一个 Promise 的实例;
  2. 在 Promise 上,有两个函数,分别叫做 resolve(成功之后的回调函数) 和 reject(失败之后的回调函数)
  3. 在 Promise 构造函数的 Prototype 属性上,有一个 .then() 方法,也就说,只要是 Promise 构造函数创建的实例,都可以访问到 .then() 方法
  4. Promise 表示一个 异步操作;每当我们 new 一个 Promise 的实例,这个实例,就表示一个具体的异步操作;
  5. 既然 Promise 创建的实例,是一个异步操作,那么,这个 异步操作的结果,只能有两种状态:
    5.1 状态1: 异步执行成功了,需要在内部调用 成功的回调函数 resolve 把结果返回给调用者;
    5.2 状态2: 异步执行失败了,需要在内部调用 失败的回调函数 reject 把结果返回给调用者;
    5.3 由于 Promise 的实例,是一个异步操作,所以,内部拿到 操作的结果后,无法使用 return 把操作的结果返回给调用者; 这时候,只能使用回调函数的形式,来把 成功 或 失败的结果,返回给调用者;
  6. 我们可以在 new 出来的 Promise 实例上,调用 .then() 方法,【预先】 为 这个 Promise 异步操作,指定 成功(resolve) 和 失败(reject) 回调函数;

new一个promise实例

1
var promise = new Promise()

// 注意:这里 new 出来的 promise, 只是代表 【形式上】的一个异步操作;
// 什么是形式上的异步操作:就是说,我们只知道它是一个异步操作,但是做什么具体的异步事情,目前还不清楚

创建一个具体的异步操作

1
var promise = new Promise(function(){
2
  // 这个 function 内部写的就是具体的异步操作!!!
3
})

// 这是一个具体的异步操作,其中,使用 function 指定一个具体的异步操作

promise读文件例子

1
var promise = new Promise(function () {
2
  fs.readFile('./files/2.txt', 'utf-8', (err, dataStr) => {
3
    if (err) throw err
4
    console.log(dataStr)
5
  })
6
})

// 每当 new 一个 Promise 实例的时候,就会立即 执行这个 异步操作中的代码
// 也就是说,new 的时候,除了能够得到 一个 promise 实例之外,还会立即调用 我们为 Promise 构造函数传递的那个 function,执行这个 function 中的 异步操作代码;

为了在我们需要的时候再去执行相应的异步操作,要将这个promise实例放在一个函数中,否则它会立即执行,代码如下:

1
const fs = require('fs')
2
function getFileByPath(fpath) {
3
  return new Promise(function (resolve, reject) {
4
    fs.readFile(fpath, 'utf-8', (err, dataStr) => {
5
      if (err) return reject(err)
6
      resolve(dataStr)
7
8
    })
9
  })
10
}
11
//调用:
12
getFileByPath('./files/2.txt')
13
  .then(function (data) {
14
    console.log(data + '-------')
15
  }, function (err) {
16
    console.log(err.message)
17
  })

调用的时候,成功或是失败的回调函数都写在.then()里。
注意: 通过 .then 指定 回调函数的时候,成功的 回调函数,必须传,但是,失败的回调,可以省略不传。

采用promise

promise将一层层的嵌套变成链式调用。现在对最开始的读取文件代码改造成promise方式依次读取三个文件。

1
const fs = require('fs')
2
function getFileByPath(fpath) {
3
  return new Promise(function (resolve, reject) {
4
    fs.readFile(fpath, 'utf-8', (err, dataStr) => {
5
      if (err) return reject(err)
6
      resolve(dataStr)
7
    })
8
  })
9
}

调用:

1
// 读取文件1
2
getFileByPath('./files/11.txt')
3
  .then(function (data) {
4
    console.log(data)
5
6
    // 读取文件2
7
    return getFileByPath('./files/2.txt')
8
  }, function (err) {
9
    console.log('这是失败的结果:' + err.message)
10
    // return 一个 新的 Promise
11
    return getFileByPath('./files/2.txt')
12
  })
13
  .then(function (data) {
14
    console.log(data)
15
    
16
  // 读取文件3
17
    return getFileByPath('./files/3.txt')
18
  })
19
  .then(function (data) {
20
    console.log(data)
21
  })

说明:1.在上一个 .then 中,返回一个新的 promise 实例,可以继续用下一个 .then 来处理
2.如果 ,前面的 Promise 执行失败,我们不想让后续的Promise 操作被终止,可以为 每个 promise 指定 失败的回调,否则它既不继续执行也不显示报错信息。

有时候,我们有这样的需求,和上面的需求刚好相反:如果 后续的Promise 执行,依赖于 前面 Promise 执行的结果,如果前面的失败了,则后面的就没有继续执行下去的意义了,此时,我们想要实现,一旦有报错,则立即终止所有 Promise的执行,并且能输出报错信息;

1
getFileByPath('./files/1.txt')
2
  .then(function (data) {
3
    console.log(data)
4
    // 读取文件2
5
    return getFileByPath('./files/22.txt')
6
  })
7
  .then(function (data) {
8
    console.log(data)
9
10
    return getFileByPath('./files/3.txt')
11
  })
12
  .then(function (data) {
13
    console.log(data)
14
  })
15
  .catch(function (err) { // catch 的作用: 如果前面有任何的 Promise 执行失败,则立即终止     所有 promise 的执行,并马上进入 catch 去处理 Promise中 抛出的异常;
16
    console.log('这是自己的处理方式:' + err.message)
17
  })

执行结果:

可以看到,因为导入的是./files/22.txt,此文件不存在,因此输出111后,能正常报错并终止后面代码的执行。

本文就到这里,如果后续还有相关知识会继续更新,欢迎评论哟~

文章作者: Byron
文章链接: https://byronk.top/2019/08/12/promise%E7%AF%87/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 byron's | BLOG
打赏
  • 微信
  • 支付宝

评论