Promise重学—基础知识

318 阅读4分钟

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

Promise 是一门新的技术,是JS中进行异步编程的心解决方案,从语法上说,Promise是一个构造函数,从功能上来说,Promise对象用了封装一个异步操作并可以获取到其成功失败的结果值。

1, 常见的异步编程

1, fs 文件操作

require('fs').readFile('./index.html', (err, data) => {})

2,数据库操作

3,AJAX

$.get('./server', (data) => {})

4,定时器

setTimeout(() => {}, 200)

2, Promise的特点

支持链式调用,可以解决回调地狱问题

(1) 文件的读取

// 以前读取文件的写法
const fs = require('fs')
fs.readFile('./package/content.txt',(err, data) => {
   // 如果错误,抛出错误
   if(err) throw err;
   //否则输出文件内容
   console.log(data.toString())
})

// 现在promise形式
let p = new Promise((resolve, reject) => {
   fs.readFile('./package/content.txt', (err, data) => {
      // 如果出错
       if(err) reject(err)
       // 如果成功
       resolve(data)
   })
})
// then里面有两个回调函数
p.then(value => {
   console.log(value.toString())
}, reason=> {
   console.log(reason)
})

(2) Promise 封装 AJAX, 将用到一个开源 api地址:

api.apiopen.top/api.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <style>
            #page {
                font-size: 24px;
            }
            #btn {
                background-color: blue;
            }
        </style>
    </head>
    <body>
      <div class="container">
          <h2 class="page">Promise 封装AJAX操作</h2>
          <button class="btn" id="btn">点击发送</button>
      </div>
      <script>
           // 接口地址: https://api.apiopen.top/getJoke
           const btn = document.querySelector('#btn')
                 btn.addEventListener('click', function() {
                    // 创建Promise
                    const p = new Promise((resolve, reject) => {
                        const xhr = new XMLHttpRequest();
                            // 初始化
                            xhr.open('GET', 'https://api.apiopen.top/getJoke')
                            // 发送
                            xhr.send()
                            // 处理响应结果
                            xhr.onreadystatechange = function() {
                                if(xhr.readyState ===4) {
                                    // 判断响应状态码 2xx
                                    if(xhr.status >=200 && xhr.status < 300){
                                        // 成功,输出响应体
                                        // console.log(xhr.response)
                                        resolve(xhr.response)
                                    }else{
                                        // 失败
                                        //console.log(xhr.status)
                                        reject(xhr.status)
                                    }
                                }
                            }
                    });
                    p.then(value =>{
                       console.log(value)
                    }, reason => {
                        console.log(reason)
                    })
                 })
      </script>
    </body>
</html>

当然也可以把AJAX的封装提取出来

function sendAjax(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url);
        xhr.send()
        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4) {
                if(xhr.status >=200 && xhr.status < 300) {
                    resolve(xhr.response)
                } else {
                    reject(xhr.status)
                }
            }
        }
    })
}

(3) 封装一个 读取文件的函数

/**
 * 封装一个函数 mineReadFile 读取文件内容
 * 参数: path  文件路径
 * 返回: promise 对象
 */
function mineReadFile(path) {
    return new Promise((resolve, reject) => {
        // 读取文件
        require('fs').readFile(path, (err, data) => {
            // 判断
            if(err) reject(err);
            // 成功
            resolve(data)
        })
    })
}
mineReadFile('./package/content.txt')
.then(value =>{
  console.log(value.toString())
},reason => {
  console.log(reason)
})

也可以通过util.promisify():传入一个遵循常见的错误优先的回调风格的函数,并返回一个返回promise的版本

/**
 * util.promisify 方法
 */
//引入util模块
const util = require('util')
const fs = require('fs')
// 返回一个新函数
let mineReadFile = util.promisify(fs.readFile);
mineReadFile('./package/content.txt').then(value => {
    console.log(value.toString())
},reason => {
    console.log(reason)
})

3, Promise 原理知识

(1)Promise的状态,实例对象中的一个属性:PromiseStatue, 一种有三只状态

  • pending 未决定的

  • resolved / fullfilled 成功

  • rejected. 失败

    状态的改变只能有两种情况,一个promise 对象只能改变一次

    1,pengding 变为resolved

    2, pending 变为 rejected

(2)Promise 对象的值, 实例对象中的一个属性: PromiseResult,保存异步任务成功/失败的结果

​ 1, resolve

​ 2, reject

4, Promise 的基本流程

graph LR
B(new Promise/pendding状态)
B-->C{执行异步操作}
C-->|成功了执行/resolve |D(promise对象/resolved状态)
C-->|失败了执行/reject|E(promise对象/rejected状态)
D-->|then方法之后 |F(回调onResolved方法)
E-->|then/catch方法之后 |H(回调onRejected方法)
F-->J{新的promise}
H-->J{新的promise}

p.then(第一个成功的回调函数, 第二个失败的回调函数)

5, Promise API

(1) Promise 构造函数 Promise(executor{})

  • executor 函数: 执行器,会在Promise内部立即同步调用
  • resolve 函数: 内部定义,成功时调用的函数 value => {}
  • reject 函数: 内部定义,失败的时调用的函数 reason => {}

(2) Promise.prototype.then 方法 (onResolved, onRejected) => {}

  • onResolved 函数: 成功的回调函数 (value) => {}
  • onRejected 函数 : 失败的回调函数 (reason)=>{}

(3)Promise.prototype. catch方法 (onRejected) => {}

​ 只能在失败的时候,回调的函数

(4)Promise.resolve()

​ 说明:Promise函数对象上的方法,和实例的方法不一样,

​ 返回一个成功/失败的promise对象

let p1 = Promise.resolve(123)
// 如果传入的参数为非Promise类型的对象,则返回的结果为成功的promise对象
// 如果传入的参数为Promsie对象,则参数的结果决定了resolve的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
      resolve('ok')
}))

(5)Promise.reject方法

​ 返回一个失败的promise对象

let p = Promise.reject(123)

(6) Promise.all方法: (promise)=>{}

说明:返回一个新的promise, 只有所有的promise都成功了才成功,只要有一个失败了就直接失败

let p1 = new Promise((resolve, reject) => {
    resolve('ok')
})
let p2 = new Promise((resolve, reject) => {
    resolve('ok')
})
let p3 = new Promise((resolve, reject) => {
    resolve('ok')
})
const result = Promise.all([p1,p2,p3])

(7)Promise.race方法, 等同于Promise.all方法,不一样的地方:

成功或者失败的状态只能由第一个Promise的结果状态决定

const result = Promise.race([p1,p2,p3])
// 由定一个p1决定

注意1: 改变Promise的状态只有三只方式:

1, resolve()

2,reject()

3, 抛出错误: throw 'err'

注意2: 如果一个Promise指定多个成功/失败回调函数,当Promise改变对应状态时都会调用

注意3: 改变Promise状态和指定回调函数谁先谁后?

​ 都有可能,正常情况下是先指定回调函数载改变状态,但是也可以先改变状态再指定回调

执行器函数里面是个异步任务的时候,先执行then, 后改变状态

注意4: promise的链式调用

then的返回也是promise对象

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('ok')
    }, 1000);
})
    p.then(value => {
        return new Promise((resolve, reject) => {
            resolve('success')
        })
    }).then(value => {
        console.log(value)  // 输出success 
    }).then(value => {
        console.log(value)  // 输出undefined
    }).catch(reason => {
      console.log(reason)
    })

注意5: 异常穿透

​ 多个.then()调用的时候,只需要在最后一个地方通过:catch() 捕获

注意6: 如何终止一个链式.then()的回调

有且只有一个方法: 返回一个 pendding状态的promise, 通过return true, throw 都不行的

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('ok')
    }, 1000);
})
    p.then(value => {
        console.log(111)
        return ;  // 终止不掉的
    }).then(value => {
        console.log(222)
        return new Promise(()=>{}) // 可以终止掉then的链式调用
    }).then(value => {
        console.log(333)
    })

6, 总结

promise的理解很重要,后续会一遍学习,一遍封装一个自己的promise类库,加深自己对promise的理解