从零开始的Promise(一)

484 阅读9分钟

从零开始的Promise(二)

从零开始的Promise(三)

1. 预备知识

1.1 实例对象与函数对象

  • 实例对象:new 函数产生的对象,称为实例对象,简称为对象
  • 函数对象:将函数作为对象使用时,称为函数对象
function Fn() { // Fn只能称为函数
}
const fn = new Fn() // Fn只有new过的才可以称为构造函数
//fn称为实例对象
console.log(Fn.prototype)// Fn作为对象使用时,才可以称为函数对象
Fn.bind({}) //Fn作为函数对象使用
$('#test') // $作为函数使用
$.get('/test') // $作为函数对象使用

()左边是函数,点左边是对象(函数对象、实例对象)

1.2 两种类型的回调函数

1. 什么是回调函数

  1. 你定义的
  2. 你没有调
  3. 最终他执行了

2. 常见的回调函数与异步操作

  • dom事件回调函数
  • 定时器回调函数、
  • ajax回调函数
  • 生命周期回调函数
  • 数据库操作
  •  fs 文件操作
require('fs').readFile('./index.html', (err,data)=>{})\

3. 同步回调

立即执行,完全执行完了才结束,不会放入回调队列中

数组遍历相关的回调 / Promise的executor函数

const arr = [1, 3, 5];
arr.forEach(item => { // 遍历回调,同步回调,不会放入队列,一上来就要执行
  console.log(item);
})
console.log('forEach()之后')

image.png

4. 异步回调

不会立即执行,会放入回调队列中将来执行

定时器回调 / ajax回调 / Promise成功或失败的回调

// 定时器回调
setTimeout(() => { // 异步回调,会放入队列中将来执行
  console.log('timeout callback()')
}, 0)
console.log('setTimeout()之后')

image.png

// Promise 成功或失败的回调
new Promise((resolve, reject) => {
  resolve(1)
}).then(
  value => {console.log('value', value)},
  reason => {console.log('reason', reason)}
)
console.log('----')
// ----
// value 1

js 引擎先把初始化的同步代码都执行完成后,才执行回调队列中的代码

1.3 JS中的异常error处理

1. 错误的类型

1. Error:所有错误的父类型

2. ReferenceError:引用的变量不存在

console.log(a) // ReferenceError:a is not defined

3. TypeError:数据类型不正确

let b
console.log(b.xxx)
// TypeError:Cannot read property 'xxx' of undefined

let c = {}
c.xxx()
// TypeError:c.xxx is not a function

4. RangeError:数据值不在其所允许的范围内

function fn() {
  fn()
}
fn()
// RangeError:Maximum call stack size exceeded

5. SyntaxError:语法错误

const c = """"
// SyntaxError:Unexpected string

2. 错误处理(捕获与抛出)

抛出错误:throw error

function something() {
  if (Date.now()%2===1) {
    console.log('当前时间为奇数,可以执行任务')
  } else { //如果时间为偶数抛出异常,由调用来处理
    throw new Error('当前时间为偶数,无法执行任务')
  }
}

捕获错误:try ... catch

// 捕获处理异常
try {
  something()
} catch (error) {
  alert(error.message)
}

3. 错误对象

  • massage 属性:错误相关信息
  • stack 属性:函数调用栈记录信息
try {
  let d
  console.log(d.xxx)
} catch (error) {
  console.log(error.message)
  console.log(error.stack)
}
console.log('出错之后')
// Cannot read property 'xxx' of undefined
// TypeError:Cannot read property 'xxx' of undefined
// 出错之后

因为错误被捕获处理了,后面的代码才能运行下去,打印出‘出错之后’

2. Promise的理解和使用

2.1 Promise是什么

1. 理解Promise

  • 抽象表达
    1. Promise 是一门新的技术(ES6 规范)
    2. Promise 是 JS 中进行异步编程的新解决方案 备注:旧方案是单纯使用回调函数
  • 具体表达
    1. 从语法上看:Promise是一个构造函数 (自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法)
    2. 从功能上看:promise对象用来封装一个异步操作并可以获取其成功/失败的结果值
  • 阮一峰的解释
    1. 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
    2. 从语法上说,Promise 是一个对象,从它可以获取异步操作的消息
    3. Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理

2. Promise 的状态的改变

实例对象promise中的一个属性 PromiseState

  1. pending 变为 resolved/fullfilled
  2. pending 变为 rejected 注意
  • 对象的状态不受外界影响
  • 只有这两种,且一个 promise 对象只能改变一次
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果
  • 无论成功还是失败,都会有一个结果数据。成功的结果数据一般称为 value,而失败的一般称为 reason。

3. Promise对象的值

实例对象promise的另一个值 PromiseResult,保存着对象 成功/失败 的值(value/reason)

  • resolve/reject可以修改值

4. Promise 的基本流程

image.png

5. Promise 的基本使用

  • 通过new创建一个Promise对象时, 接受一个函数(执行器函数--executor)作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

  • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,会执行promise对象的then方法传递过来的第一个参数(resolved回调函数), 并将异步操作的结果,作为参数value传递出去;

  • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,会执行promise对象的then方法传递过来的第二个参数(rejected回调函数),并将异步操作报出的错误,作为参数error/reason传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) { // onResolved
  // success
}, function(reason) { // onRejected
  // failure
});

then方法可以接受两个回调函数作为参数

  • 第一个回调函数onResolved()是Promise对象的状态变为resolved时调用
  • 第二个回调函数onRejected()是Promise对象的状态变为rejected时调用
  • 这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数

.then()方法 和执行器(executor)都是同步执行的,.then()中的回调函数是异步执行的

1) 使用 1: 基本编码流程

  // 创建 promise 对象(pending 状态), 指定执行器(executor)函数
  const promise = new Promise((resolve, reject) => {
    // 接受executor(执行器)函数作为new Promise()的参数, 这个执行器函数会被立即执行
    console.log("立即执行");
    setTimeout(() => {
      const time = Date.now();
      // 如果成功了, 调用 resolve(), 执行then传递过来的回调函数, 指定成功的 value值, 且promise变为 resolved 状态
      if (time % 2 === 1) {
        //  promise的状态一旦确定下来, 就不会再更改, 即多次调用resolve/reject无效, 只执行第一个
        resolve("成功的值 " + time);
      } else {
        // 如果失败了, 调用 reject(), 执行then传递过来的回调函数, 指定失败的 reason值, 且promise变为rejected 状态
        reject("失败的值" + time);
      }
    }, 2000);
  });

  promise.then(
    (value) => { // 传入的回调函数, 当Promise的状态为resolved/fullfilled的时候调用resolve方法, 执行这个回调
      console.log("成功的回调:", value);
    },
    (reason) => { // 传入的回调函数, 当Promise的状态为rejected的时候调用reject方法, 执行这个回调
      console.log("失败的回调:", reason);
    }
  );
  console.log(".then方法是立即执行的, 但then方法内的回调是异步执行的");
  // 立即执行
  // .then方法是立即执行的, 但then方法内的回调是异步执行的
  // 失败的回调: 失败的值1677679498530

2) 使用 2: 使用 promise 封装基于定时器的异步

function doDelay(time) {
    // 1. 创建 promise 对象
    return new Promise((resolve, reject) => {
        // 2. 启动异步任务
        console.log('启动异步任务')
        setTimeout(() => {
            console.log('延迟任务开始执行...')
            const time = Date.now() // 假设: 时间为奇数代表成功, 为偶数代表失败
            if (time % 2 === 1) { // 成功了
                // 3. 1. 如果成功了, 调用 resolve()并传入成功的 value
                resolve('成功的数据 ' + time)
            } else { // 失败了
                // 3.2. 如果失败了, 调用 reject()并传入失败的 reason
                reject('失败的数据 ' + time)
            }
        }, time)
    })
}
const promise = doDelay(2000)
promise.then(
    value => { // 传入回调函数, 等待成功时调用
        console.log('成功的 value: ', value)
    },
    reason => { // 传入回调函数, 等待失败时调用
        console.log('失败的 reason: ', reason)
    },
)

3) 使用 3: 使用 promise 封装 ajax 异步请求

  • 可复用的发 ajax 请求的函数: xhr + promise
function promiseAjax(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.onreadystatechange = () => {
            if (xhr.readyState !== 4) return
            const { status, response } = xhr
            // 请求成功, 调用 resolve(value)
            if (status >= 200 && status < 300) {
                resolve(JSON.parse(response))
            } else { // 请求失败, 调用 reject(reason)
                reject(new Error('请求失败: status: ' + status))
            }
        }
        xhr.open("GET", url)
        xhr.send()
    })
}
promiseAjax('https://api.apiopen.top2/getJoke?page=1&count=2&type=video').then(
    data => {
        console.log('显示成功数据', data)
    },
    error => {
        alert(error.message)
    }
)

2.2 为什么要用 Promise?

1. 指定回调函数的方式更加灵活

  1. 旧的: 必须在启动异步任务前指定
  2. promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)

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

1. 什么是回调地狱?

回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调执行的条件

image.png

2. 回调地狱的缺点?

不便于阅读 不便于异常处理

3. 解决方案?

promise 链式调用

4. 终极解决方案?

async/await

    /*
    1. 指定回调函数的方式更加灵活:
    旧的: 必须在启动异步任务前指定
    promise: 启动异步任务 => 返回 promie 对象 => 给 promise 对象绑定回调函数
    (甚至可以在异步任务结束后指定)
    2. 支持链式调用, 可以解决回调地狱问题
    什么是回调地狱? 回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函
    数执行的条件
    回调地狱的缺点? 不便于阅读 / 不便于异常处理
    解决方案? promise 链式调用
    终极解决方案? async/await
    */
    // 成功的回调函数
    function successCallback(result) {
        console.log("声音文件创建成功: " + result);
    }
    // 失败的回调函数
    function failureCallback(error) {
        console.log("声音文件创建失败: " + error);
    }
    /* 1.1 使用纯回调函数 */
    createAudioFileAsync(audioSettings, successCallback, failureCallback)
    /* 1.2. 使用 Promise */
    const promise = createAudioFileAsync(audioSettings); // 2
    setTimeout(() => {
        promise.then(successCallback, failureCallback);
    }, 3000);
    /*
    2.1. 回调地狱
    */
    doSomething(function (result) {
        doSomethingElse(result, function (newResult) {
            doThirdThing(newResult, function (finalResult) {
                console.log('Got the final result: ' + finalResult)
            }, failureCallback)
        }, failureCallback)
    }, failureCallback)
    /*
    2.2. 使用 promise 的链式调用解决回调地狱
    */
    doSomething().then(function (result) {
        return doSomethingElse(result)
    })
        .then(function (newResult) {
            return doThirdThing(newResult)
        })
        .then(function (finalResult) {
            console.log('Got the final result: ' + finalResult)
        })
        .catch(failureCallback)
    /*
    2.3. async/await: 回调地狱的终极解决方案
    */
    async function request() {
        try {
            const result = await doSomething()
            const newResult = await doSomethingElse(result)
            const finalResult = await doThirdThing(newResult)
            console.log('Got the final result: ' + finalResult)
        } catch (error) {
            failureCallback(error)
        }
    }

3. 代码实例

Promise实现定时器-抽奖

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>基本使用</title>
    <link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
        rel="stylesheet">
</head>

<body>
    <div class="container">
        <h2 class="page-header">Promise 初体验</h2>
        <button class="btn btn-primary" id="btn">点击抽奖</button>
    </div>
    <script>
        //生成随机数
        function rand(m, n) {
            return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
        }
        /**
            点击按钮,  1s 后显示是否中奖(30%概率中奖)
                若中奖弹出    恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券
                若未中奖弹出  再接再厉
        */
        //获取元素对象
        const btn = document.querySelector('#btn');
        //绑定单击事件
        btn.addEventListener('click', function () {
            //定时器
            // setTimeout(() => {
            //     //30%  1-100  
            //     //获取从1 - 100的一个随机数
            //     let n = rand(1, 100);
            //     //判断
            //     if(n <= 30){
            //         alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券');
            //     }else{
            //         alert('再接再厉');
            //     }
            // }, 1000);

            //Promise 形式实现
            // resolve 解决  函数类型的数据
            // reject  拒绝  函数类型的数据
            const p = new Promise((resolve, reject) => {
                setTimeout(() => {
                    //30%  1-100  1 2 30
                    //获取从1 - 100的一个随机数
                    let n = rand(1, 100);
                    //判断
                    if (n <= 30) {
                        resolve(n); // 将 promise 对象的状态设置为 『成功』
                    } else {
                        reject(n); // 将 promise 对象的状态设置为 『失败』
                    }
                }, 1000);
            });

            console.log(p);
            //调用 then 方法
            // value 值
            // reason 理由
            // 上面两个值随便写也行,但最好不要乱写
            p.then((value) => {
                // 对象状态为成功时的回调
                alert('恭喜恭喜, 奖品为 10万 RMB 劳斯莱斯优惠券, 您的中奖数字为 ' + value);
            }, (reason) => {
                // 对象状态为失败时的回调
                alert('再接再厉, 您的号码为 ' + reason);
            });
        });
    </script>
</body>

</html>

Promise实现AJAX

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 封装 AJAX</title>
    <link crossorigin='anonymous' href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
        rel="stylesheet">
</head>

<body>
    <div class="container">
        <h2 class="page-header">Promise 封装 AJAX 操作</h2>
        <button class="btn btn-primary" id="btn">点击发送 AJAX</button>
    </div>
    <script>
        //接口地址 https://api.apiopen.top/getJoke
        //获取元素对象
        const btn = document.querySelector('#btn');

        btn.addEventListener('click', function () {
            //创建 Promise
            const p = new Promise((resolve, reject) => {
                //1.创建对象
                const xhr = new XMLHttpRequest();
                //2. 初始化
                xhr.open('GET', 'https://api.apiopen.top/getJoke');
                //3. 发送
                xhr.send();
                //4. 处理响应结果
                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);
                        }
                    }
                }
            });
            //调用then方法
            p.then(value => {
                console.log(value);
            }, reason => {
                console.warn(reason);
            });
        });
    </script>
</body>
</html>

使用 promise 封装 ajax 异步请求

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise封装AJAX操作</title>
</head>

<body>
    <script>
        /**
         * 封装一个函数 sendAJAX 发送 GET AJAX 请求
         * 参数   URL
         * 返回结果 Promise 对象
         */
        function sendAJAX(url) {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.responseType = 'json';
                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);
                        }
                    }
                }
            });
        }

        sendAJAX('https://api.apiopen.top/getJoke')
            .then(value => {
                console.log(value);
            }, reason => {
                console.warn(reason);
            });
    </script>
</body>
</html>