Promise 学习

159 阅读37分钟

Promise 学习

Promise 是 ES6中新的异步编程解决方案。

从语法角度来看,Promise就是一个构造函数,可以封装异步的任务,并处理结果。

好处,解决**“回调地狱”**问题。

一、Promise 介绍与基本使用

1、Promise 是什么?

抽象表达:
	Promise 是一门新的技术,是JS中进行异步编程的新解决方案。【旧的解决方案就是单纯的回调而已】
具体表达:
	Promise 从语法角度就是一个构造函数,Promise对象用来封装一个异步操作并可以获取其成功/失败的结果值。
	
异步操作都有哪些呢?【包括但不限于】
- fs文件操作 (NodeJs下面的一个模块,可以操作磁盘进行读写操作)
- 数据库操作
- Ajax 网络请求
- 定时器 

2、为什么要用 Promise 呢?

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

回调地狱:
一个回调函数中,套着另一个异步任务。特点代码不停的缩进。
缺点:不易阅读、不方便错误处理。
asyncFuncl(opt, (...args1) => {
	asyncFunc2(opt, (...args1) => {
		asyncFunc3(opt, (...args1) => {
            asyncFunc4(opt, (...args1) => {
            	// some operation
            })
		})
	})
})

2、指定回调函数的方式更加灵活。

之前:必须在启动异步任务前指定。
Promise:启动异步任务 → 返回 promise对象 → 给 promise 对象绑定回调函数(甚至可以在异步任务结束后指定多个)

3、Promise初体验

需求:点击按钮进行抽奖,点击按钮2s后弹出结果。中间概率是30%。

传统写法:

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 初体验</title>
</head>

<body>
    <h1>Promise 初体验</h1>
    <button>点击抽奖</button>

    <script>
        // 生成随机数函数
        let createRandom = function (min, max) {
            let number = Math.ceil(Math.random() * (max - min + 1)) + min - 1;
            return number;
        }

        // 获取元素
        let btn = document.getElementsByTagName('button')[0];
        // 需求:
        /*
        1、点击按钮 2s后显示结果。【使用定时器 setTimeout】
        2、要求中奖记录在30%。【取巧办法,范围是1-100,只要随机值 小于等于30 就是30%的概率】
        */
        btn.onclick = function () {
            setTimeout(function () {
                let number = createRandom(1, 100);
                if (number <= 30) {
                    alert(`${number},恭喜你中奖了`)
                } else {
                    alert(`${number},很遗憾,你没有中奖`)
                }
            }, 2000)
        }
    </script>
</body>

</html>

Promise 写法

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 初体验</title>
</head>

<body>
    <h1>Promise 初体验</h1>
    <button>点击抽奖</button>

    <script>
        // 生成随机数函数
        let createRandom = function (min, max) {
            let number = Math.ceil(Math.random() * (max - min + 1)) + min - 1;
            return number;
        }

        // 获取元素
        let btn = document.getElementsByTagName('button')[0];
        // 需求:
        /*
        1、点击按钮 2s后显示结果。【使用定时器 setTimeout】
        2、要求中奖记录在30%。【取巧办法,范围是1-100,只要随机值 小于等于30 就是30%的概率】
        */
        btn.onclick = function () {
            // 第一步:先实例化 Promise
            /*
            需要接收一个参数,这个参数是一个函数的形式,可以包裹一个异步操作
            函数还有两个形参,resolve 和 reject 
            resolve:成功的返回值,是一个函数类型的数据
            reject:拒绝的返回值,是一个函数类型的数据
            */
            let myPromise = new Promise((resolve, reject) => {
                setTimeout(function () {
                    let number = createRandom(1, 100);
                    if (number <= 30) {
                        // 当成功的时候调用 resolve,可以将 Promise对象的状态设置为成功
                        resolve(number);
                    } else {
                        // 当失败的时候调用 reject,可以将 Promise对象的状态设置为失败
                        reject(number);
                    }
                }, 2000)
            });


            // 第二步:调用 then 方法
            /*
            then() 方法是Promise对象的方法,
            该方法需要接受两个参数,两个参数都是函数
            第一个函数:是对象成功时的回调
            第二个函数:是对象失败时的回调
            */
            myPromise.then((number) => {
                alert(`${number},恭喜你中奖了`)
            }, (number) => {
                alert(`${number},很遗憾,你没有中奖`)
            })
        }
    </script>
</body>

</html>

总结:

  1. Promise 是一个构造函数,使用的时候就需要创建实例。
  2. 创建 Promise 的实例的时候,需要接受一个参数。参数是一个函数,可以包裹一个异步操作,而且函数有两个形参分别是:resolve 和 reject。这两个形参对应的也是两个函数,resolve是成功的返回,并且可以将 Promise对象的状态改为成功,reject是失败的返回,可以将 Promise对象的状态改为失败。
  3. 创建完 Promise 的实例后,就需要调用 Promise 对象的 then 方法,即可调用 Promise实例。从而执行异步操作。
  4. then 方法接受两个参数,分别都是函数,第一个函数是对象状态成功时的回调函数,第二个函数是对象状态失败的回调函数。

4、Promise 异步编程的实践练习

fs模块读取文件

const fs = require('fs');

const myPromise = new Promise((resolve, reject)=>{
    fs.readFile('./resource/content.txt', (err, data) => {
        // 如果发生错误
        reject(err);
        // 如果成功
        resolve(data);
    });
});

myPromise.then((data)=>{
    console.log(data.toString());
}, (err)=>{
    console.log(err);
})

封装 Ajax 请求

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise 封装 Ajax</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.1/css/bootstrap.min.css" rel="stylesheet">

</head>

<body>
    <div class="container">
        <h2 class="page-header">Promise 封装 Ajax</h2>
        <button class="btn btn-primary">点击发送 Ajax</button>
    </div>

    <script>
        // 接口地址 https://api.apiopen.top/getJoke
        let btn = document.querySelector('.btn');
        btn.addEventListener('click', () => {
            // >>> 使用 Promise 封装
            const myPromise = new Promise((resolve, reject) => {
                // 创建xhr对象
                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) {
                            resolve(xhr.response);
                        } else {
                            reject(xhr.status);
                        }
                    }
                }
            });

            myPromise.then((response) => {
                console.log(response);
            }, (status) => {
                console.log('请求失败,状态码为:', status);
            })
        })
    </script>
</body>

</html>

5、Promise 封装练习-fs读取文件操作

基于 node.js 运行

function mineReadFile(path) {
    return new Promise((resolve, reject) => {
        require('fs').readFile(path, (err, data) => {
            if (err) reject(err);
            resolve(data);
        });
    });
}

mineReadFile('./resource/content.txt')
    .then((data) => {
        console.log(data.toString());
    }, (err) => {
        console.log(err);
    });

6、Node.js 中 util.promisify 方法

采用遵循常见的错误优先的回调风格的函数(也就是将 (err, value) => ... 回调作为最后一个参数),并返回一个返回 promise 的版本。

例子:

这里以 fs 模块为例

// 引入util模块
const util = require('util');

// 引入fs模块
const fs = require('fs');

// 调用 mineReadFile 返回一个 Promise 对象
let mineReadFile = util.promisify(fs.readFile)

mineReadFile('./resource/content.txt').then(value => {
    console.log(value.toString());
}, err => {
    console.log(err);
})

7、Promise 封装练习 - 封装 Ajax 请求

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</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/getJack').then(value => {
            console.log(value);
        }, reason => {
            console.warn(reason);
        })


    </script>
</body>

</html>

二、Promise 具体了解

1、Promise 的状态

Promise 的状态 (PromiseState) 分为是那种状态:

  • 未决定的:pending
  • 成功:resolved / fulfilled
  • 失败:rejected

PromiseState 只可能从 pending 转为 resolved 或 rejected。不可能resolved 和 rejected 互相转换。

Promise对象的状态只能改变一次,无论变为成功还是失败,都会有一个结果数据,成功的结果一般称为 value,失败的结果一般称为 reason。

2、Promise 对象结果属性 - PromiseResult

实例对象中的另一个属性 [ PromiseResult ]

PromiseResult 保存着对象 成功/失败 的结果。

可以修改 PromiseResult 值的办法:

  • resolve函数
  • reject函数

通过 resolve 和 reject 函数修改的值,可以在 then 方法回调中,对值进行相关的操作。

3、Promise 的基本流程

  • 创建 Promise 对象,此时 PromiseState 状态为 pending
  • 执行异步操作
    • 成功了,执行 resolve()。PromiseState状态为 resolved
      • 通过 then() 方法,执行对应的回调函数,返回新的 Promise对象
    • 失败了,执行 reject()。PromiseState状态为 rejected
      • 通过 then() 方法,执行对应的回调函数,返回新的 Promise对象

三、Promise 的 API

1、Promise 构造函数:Promise(excutor)

  1. excutor 函数:执行器 (resolve, reject) => {}
  2. resolve 函数:内部定义成功时调用的函数 value => {}
  3. reject 函数:内部定义失败时调用的函数 reason => {}

说明:excutor 会在 Promise 内部立即同步调用,异步操作在执行器中执行。

同步调用是指,当代码执行到 excutor的时候,里面的所有代码会立即执行不会加入队里中等待执行。

2、Promsie.prototype.then 方法

then(onResolved, onRejected);

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

说明:指定用来得到 成功 value 的成功回调和用于得到失败 reason 的失败回调返回一个新的 promise 对象。

3、Promise.prototype.catch 方法

catch(onRejected);

  1. onRejected 函数:失败的回调函数 (reason) => {}

说明:catch方法只能指定失败的回调,返回的是一个新的 promise 对象。

是 then 方法的语法糖,相当于 then(undefined, onRejected);

4、Promise.resolve 方法

Promise.resolve(value);

  1. value:成功的数据 或 promise对象

说明:返回一个 成功/失败的 promise 对象

作用:能够快速得到一个 promise对象,还能封装一个值,将值转化为 promise对象

let p1 = Promise.resolve(123);
console.log(p1);

结果:
[[Prototype]] : Promise
[[ProtoState]] : "fulfilled"		// 成功状态
[[PromiseResult]] : 123


如果传入的参数是非Promise类型的对象,返回的结果为成功状态的Promise对象
如果传入的参数是Promise类型的对象,则返回的Promise对象的状态、值都和传入的参数一致。

5、Promise.reject 方法

Promise.reject(reason);

  1. reason:失败的原因

说明:返回一个失败的 Promise 对象。

let p1 = Promise.reject(521);
console.log(p1);

结果:
[[Prototype]] : Promise
[[ProtoState]] : "rejected"		// 成功状态
[[PromiseResult]] : 521

无论传入什么样类型的参数,包括一个成功的promise对象,返回的结果都是一个状态为失败的 promise对象。

6、Promise.all 方法

Promise.all(Array);

  1. Array:包含 n个 promise 的数组。

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

成功的结果是所有 promise 对象的值组成的数组。

失败的结果是数组中所有promise对象第一个状态为失败的promise对象的值。

let p1 = new Promise((resolve, reject) => {
    resolve('ok');
})

let p2 = Promise.resolve('Success');

let p3 = Promise.resolve('Oh Yeah');

const result = Promise.all([p1, p2, p3]);
console.log(result);
// 结果:
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: ['ok', 'Success', 'Oh Yeah']


let p4 = Promise.reject('Error-p4');
let p5 = Promise.reject('Error-p5');
const result2 = Promise.all([p1, p2, p3, p4, p5]);
// 结果:
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: 'Error-p4'

7、Promise.race 方法

Promsie.race( Array );

  1. Array:包含n个promise对象的数组

说明:返回一个新的 promise,第一个完成的 promsie 的结果状态就是最终的结果状态。

let p1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('ok');
    }, 2000)
})

let p2 = Promise.resolve('Success');

let p3 = Promise.resolve('Oh Yeah');

const result = Promise.race([p1, p2, p3]);

// 结果:
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "Success"

四、Promise 关键问题

1、如何改变 promise 的状态:

通过 resolve(value) 将状态从 pending改为 resolveed / fulfilled

通过 reject(reason) 函数将状态从 pending改为 rejected

throw 抛出异常;如果当前是pending 就会改变为 rejected

2、一个 promise 指定多个成功/失败回调函数,都会调用吗?

也就是使用 then 方法为promise对象指定多个回调,这些回调是不是都会执行?

只要 promise 对象的状态发生改变了,对应的回调函数都会被调用。

3、改变 promise 状态和指定回调函数谁先谁后?

promise对象运行时,是改变状态先执行还是 then/catch 方法指定回调先执行?

1、都有可能,正常情况下是先指定回调再改变状态,但也可能先改状态再指定回调

2、如果先改状态再执行回调?
(1)在执行器中直接调用 resolve() / reject()
(2)延迟更长时间才调用 then()

3、什么时候才能得到数据?、
(1)如果先指定的回调,那当状态发生改变时,改变状态后再去调用 成功/失败对应的函数,得到数据
(2)如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据


【俗话讲】
直接调用 resolve() 或 reject() 函数的时候,会先执行改变状态,然后在执行回调。
当 resolve() 或 reject() 函数在异步任务中,例如 setTimieout()。就会先执行回调,然后再改变状态。
【问】
先执行了回调,为什么在 then() 没有执行里面的内容呢?
因为then()方法里面的参数是两个函数,只有当状态改变了才会触发的。第一个参数表示状态为成功时执行的,第二个参数表示状态为失败时执行的。

4、 promise.then() 返回的新 proimise 的结果状态由什么决定?

1、简单表达:由 then() 指定的回调函数执行的结果决定

2、详细表达:
	(1)如果抛出异常,新 promise 变为 rejected, reason 为抛出的异常
	(2)如果返回的是 非 promise 对象,新 promise 变为 resolved,value为返回的值
	(3)如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果。
	

【实例代码:】
let myPromise = new Promise((resolve, reject) => {
	resolve('ok');
});

let result = myPromise.then((value) => {
	// 情况1:抛出异常
	// throw '出错了~';
	
	// 情况2:返回的结果是非 promise 对象
	// return 521;
	
	// 情况3:返回的结果是 promise 对象
	return new Promise((resolve, reject) => {
		resolve('success');
	});
}, (reason) => {});

console.log(result);

【总结】:
情况1中,抛出异常,则then方法返回的promise对象是 rejected 状态。
情况2中,返回非promise对象,则then方法返回的promise对象是 fulfilled 状态。
情况3中,返回的promise对象,则then方法返回的promise对象是和返回的promise对象的状态保持一致。

5、promise 如何串联多个操作任务?

1、promise 的 then() 返回一个新的 promise 可以开成 then() 的链式调用。
2、通过 then 的链式调用串联多个同步/异步任务。

【实例代码:】
let myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('ok');
    }, 1000)
})

myPromise.then(value => {
    return new Promise((resolve, reject) => {
        resolve('success');
    });
}, reason => {}).then(value => {
    console.log(value);
}, reason => {}).then(value => {
    console.log(value);		// undefiend 
}, reason => {})

为什么在第三次执行 then() 方法的时候,返回undefiend呢?因为第二次的then()方法并没有返回任何值,所以是 undefiend。
第二次调用then() 返回的是一个 状态为 fulfilled,值为 undefined 的promise对象。

6、promise 异常穿透

什么是异常穿透?
当使用 promise 的 then() 链式调用时,可以在最后调用失败的回调函数。
如果前面的then()方法有抛出异常或者返回失败的,都会直接跳到最后执行失败的调用。


【示例代码:】
let myPromise = new Promise((resolve, reject) => {
	// resolve('ok');
    reject('Error');
})

myPromise.then(value=>{
	console.log('111');
}).then(value=>{
    console.log('222');
}).then(value=>{
    console.log('333');
}).catch(reason=>{
    console.log(reason);
});
上面的代码。如果是执行的 resolve() 则控制台输出:111 222 333。
如果是执行的 reject() 则控制只输出 Errorlet myPromise = new Promise((resolve, reject) => {
	resolve('ok');
})

myPromise.then(value=>{
	console.log('111');
    throw '失败了';
}).then(value=>{
    console.log('222');
}).then(value=>{
    console.log('333');
}).catch(reason=>{
    console.log(reason);
});
上面的代码。最终在控制台输出的是:失败了。 当第一个then()方法抛出异常后直接调用中间的,去到了最后的catch()方法中。[ps: 这个catch可以写成then(reason=>{...]}) ]

7、中断 promise 链

当使用 promise 的 then 链式调用时,在中间中断,不在调用后面的回调函数

办法:在回调函数中返回一个 pending 状态的 promise 对象。

【示例代码:】
let myPromise = new Promise((resolve, reject) => {
	resolve('ok');
})

myPromise.then(value=>{
	console.log('111');
}).then(value=>{
    console.log('222');
    // 有且只有一种方法
    return new Promise(()=>{});
}).then(value=>{
    console.log('333');
}).catch(reason=>{
    console.log(reason);
});

new Promise(()=>{}) 这行代码就是生成一个状态为 pending 的promise对象。

五、Promise自定义封装

index.html

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./promise.js"></script>
</head>

<body>
    <script>
        let promise = new Promise((resolve, reject) => {
            resolve('OK');
        })

        promise.then(value => {
            console.log(value);
        }, reason => {
            console.warn(reason);
        })
    </script>
</body>

</html>

1、定义基本整体结构

promise.js

function Promise(executor) {
     
}

// 添加 then() 方法
Promise.prototype.then = function (onResolve, onReject) {

}


【解析】
Promise函数 需要接受一个执行器函数,所以设置了形参 executor

then方法,需要接受两个执行函数,分别对相应状态的 fullfillend 和 rejected,所以设置了形参 onResolve 和 onReject。

2、resolve 和 reject 结构搭建

function Promise(executor) {
    // 声明 resolve 函数
    function resolve(data) { };

    // 声明 reject 函数
    function reject(data) { };

    // 同步调用 [执行器函数]
    executor(resolve, reject);
}


【解析】
1、在 Promise 官方文档中说了,执行器函数是同步调用的,所以需要在 自定义的Promise中调用执行器函数:execuor();
2、但是,在实例化Promise对象的时候,直接在执行器函数中使用了 resolve() 或 reject(),所以也需要在 执行器函数中写上这两个函数对应的形参,为了方便理解形参也形成 resolve 和 reject。
3、目前情况下,使用自定义的Promise时会报错,resolve is not defined【这里参考下 index.html 因为使用了resolve()】
4、为了解决第3点出现的问题,我们需要在同步调用之前先声明出 resolve 和 reject 函数,这样才不会报错。

3、resolve 和 reject 功能实现

function Promise(executor) {
    // 添加属性 
    this.promiseState = 'pending';
    this.promiseReult = null;
    
    // 保存实例对象的 this 值
    const self = this;
    
    function resolve(data) {
        // 1、修改对象的状态(promiseState)
        self.promiseState = 'fulfilled';
        // 2、设置对象结果值(promiseResult)
        self.promiseResult = data;
    };

    function reject(data) {
        // 1、修改对象的状态(promiseState)
        self.promiseState = 'rejected';
        // 2、设置对象结果值(promiseResult)
        self.promiseResult = data;
    };

    executor(resolve, reject);
}

【解析:】
1resolve() 和 reject() 两个函数的主要作用是 修改对象的状态和设置对象结果值。所以在函数颞部主要修改状态和设置值。
2、因为修改后的值是返回给实例对象的,所以这里要修改的是 Promise 的状态和值。所以先在 Promise 内部声明 promiseState 默认值是pending 和 promiseResult 默认值是 null3、正式在 resolve() 和 reject() 内部修改 promiseState 和 promiseReust。this.promiseState = 'fulfilled' , this.promiseResult = data。
4、这样运行代码会出现一个问题,最终实例对象内的 promiseState 和 promiseReust 的值都还是默认值,并没有修改。这是为什么呢?因为resolve() 和 reject() 内部的this指向的 window,这里给window对象修改值了。
5、解决上面发现的问题,需要使用 const self = this; 保存实例对象的 this值。这里 self 一般都是使用 self _this that 代替。
6、使用第5点的解决办法,现在 self 就是实例对象了,里面有两个属性分别是 promiseState 和 promiseReust。这时候要在 resolve() 和 reject() 函数内部修改 self 里面的两个属性,所以需要将 this 替换成 self。这样 在第4点遇到的问题就解决了,最终修改状态和设置结果值成功。

4、thorw 抛出异常改变状态和结果值

function Promise(executor) {
    this.promiseState = 'pending';
    this.promiseResult = null;

    const self = this;

    function resolve(data) {
        self.promiseState = 'fulfilled';
        self.promiseResult = data;
    };

    function reject(data) {
        self.promiseState = 'rejected';
        self.promiseResult = data;
    };
	
    // 使用 try...catch... 解决 throw 抛出异常
    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}


【解析:】
1、当在实例内部使用 throw 抛出异常,也就是下面的情况。这样是代码会直接抛出异常并终止程序,后面的代码就不运行了。
let promise = new Promise((resolve, reject) => {
    throw '错误'
});

2、显然这并不是我想要的效果,我想要的是当使用 throw 抛出异常后,将 promise 对象的状态设置为 rejected 值为抛出的错误信息。

3、怎么才能达到 第2点 说的效果呢? 那么就先看 throw 是在哪里使用的,发现是在实例化对象的时候,在 Promise 构造函数中里面的箭头函数内部使用的,这个箭头函数就是执行器函数。

4、在 第4点 知道了是在执行器函数中使用了 throw 抛出异常,那么可以使用 try...catch... 来检测这个异常。 try...catch...要写在执行器函数执行的那一步,所以要写在 Promise 构造函数内部的。

5、下面代码这样,就可以检测 throw 抛出的异常,抛出的错误信息会被 catch的形参e来接收,当 throw 抛出异常了,直接调用 reject()函数,把 e 这个错误信息当做参数传入进去。这样就可以将状态改为 rejected,并且结果值是 抛出的错误信息。
try {
    executor(resolve, reject);
} catch (e) {
    reject(e);
}

5、Promise对象状态只能修改一次

function Promise(executor) {
    this.promiseState = 'pending';
    this.promiseResult = null;

    const self = this;

    function resolve(data) {
        // 通过判断来设定,状态只能修改一次
        if (self.promiseState !== 'pending') return;
        
        self.promiseState = 'fulfilled';
        self.promiseResult = data;
    };

    function reject(data) {
        // 通过判断来设定,状态只能修改一次
        if (self.promiseState !== 'pending') return;
        
        self.promiseState = 'rejected';
        self.promiseResult = data;
    };

    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

【解析:】
通过使用if语句判断,promiseState 这个属性的值是否还是 pending。如果不是则证明状态修改过了,直接return结束函数就可以了。

6、then 方法中执行回调

Promise.prototype.then = function (onResolve, onReject) {
    // 通过 if语句进行判断调用对应的回调函数
    if (this.promiseState === 'fulfilled') {
        onResolve(this.promiseResult);
    };
    if (this.promiseState === 'rejected') {
        onReject(this.promiseResult);
    }
}

【解析:】
1、通过使用if语句来判断什么时候调用 onResolve 函数,什么时候调用 onReject 函数。
2、判断条件肯定是来看promise对象的状态,如果状态成功调用 onResolve 函数,如果状态失败调用 onReject 函数。
3、为什么是 this.promiseState 呢? 因为调用 then 方法的就是promise的实例对象,可以通过 this 来查看内部的属性。
4、将 this.promiseReuslt 结果值作为 函数的参数,这样就可以返出去结果值。

7、异步任务回调的执行

function Promise(executor) {
    this.promiseState = 'pending';
    this.promiseResult = null;
    // 添加一个 callback 属性
    this.callback = {};

    const self = this;
    
    function resolve(data) {
        if (self.promiseState !== 'pending') return;
        self.promiseState = 'fulfilled';
        self.promiseResult = data;
        // 判断,如果callback属性中有 onResolve 调用成功时的回调
        if (self.callback.onResolve) {
            self.callback.onResolve(data);
        };
    };

    function reject(data) {
        if (self.promiseState !== 'pending') return;
        self.promiseState = 'rejected';
        self.promiseResult = data;
        // 执行异步任务的回调函数
        if (self.callback.onReject) {
            self.callback.onReject(data);
        };
    };

    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }

}


Promise.prototype.then = function (onResolve, onReject) {
    if (this.promiseState === 'fulfilled') {
        onResolve(this.promiseResult);
    };
    if (this.promiseState === 'rejected') {
        onReject(this.promiseResult);
    };
    
    // 执行异步任务的回调函数
    if (this.promiseState === 'pending') {
        this.callback = {
            onResolve: onResolve,
            onReject: onReject
        };
    };
}


【解析:】
1、运行下面的代码
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('OK');
    }, 1000)
})

promise.then(value => {
    console.log(value);
}, reason => {
    console.warn(reason);
})
上面的代码,同步运行,运行到调用 then 方法的时候,因为执行器函数内部是异步函数导致 promise实例对象的 promiseState 的状态还为 pending 的状态,但是在 Promise的代码中发现,调用 then 方法后并没有 promiseState 状态为 pending 的对应的操作。

2、既然上面发现了问题,在代码中加入即可。所以对 promiseState 状态是否为 pending 进行判断。

3、问题来了,既然执行器函数中是异步任务,那么我们在哪里调用 onResolve 和 onReject 这两个函数合适呢? 答案是在 Promise 构造器函数内部的 resolve 和 reject 这两个方法中最合适不过了。

4、那么如何才能让 onResolve 和 onReject 这两个函数在 resolve 和 reject 这两个方法中能调用? 答案:直接给对象本身增加一个 callback的属性,里面存放着 onResolve 和 onReject。 当 promiseState 的状态为 pending 的时候,将 onResolve 和 onReject 这个两个函数存放到 callback 这个属性中。

5、既然已经有了 callback 这个属性了,那么就开始使用它好了。在 resolve 和 reject 最后可以进行判断,在 callback中是否有onResolve 和 onReject 如果有了则证明执行器函数内部是异步任务,则直接执行 callback 属性内的对应的 onResolve 或 onReject,他们的值可以使用结果值属性,也可以是赋值给结果值属性的那个值。

8、指定多个回调的实现

到现在为止,如果多次调用promise的then方法,会出现一个问题,只有最后一次的调用生效。为了避免这个问题做出了对应的解决方案。

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('OK');
        // reject('Error')
        // throw '发生错误了'
    }, 1000)


})

console.log(promise);

promise.then(value => {
    console.log(value);
}, reason => {
    console.warn(reason);
})

promise.then(value => {
    alert(value);
}, reason => {
    alert(reason);
})

上面代码执行的时候,只有最后一次调用then 方法会生效。

下面是解决方案:

function Promise(executor) {
    this.promiseState = 'pending';
    this.promiseResult = null;
    // 添加一个 callback 属性
    this.callbacks = [];

    const self = this;
    
    function resolve(data) {
        if (self.promiseState !== 'pending') return;
        self.promiseState = 'fulfilled';
        self.promiseResult = data;
        // 执行异步任务的回调函数
        self.callback.forEach(item => {
            item.onResolve(data);
        })
    };

    function reject(data) {
        if (self.promiseState !== 'pending') return;
        self.promiseState = 'rejected';
        self.promiseResult = data;
        // 执行异步任务的回调函数
        self.callback.forEach(item => {
            item.onReject(data);
        })
    };

    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }

}


Promise.prototype.then = function (onResolve, onReject) {
    if (this.promiseState === 'fulfilled') {
        onResolve(this.promiseResult);
    };
    if (this.promiseState === 'rejected') {
        onReject(this.promiseResult);
    };
    
    // 执行异步任务的回调函数
    if (this.promiseState === 'pending') {
        this.callbacks.push({
            onResolve: onResolve,
            onReject: onReject
        });
    };
}


【分析:】
1、为什么会遇到那样的问题呢? 主要原因是:两次调用then 方法的时候,因为执行器函数内是异步任务,所以两次调用时 promise实例对象的状态都是 pending 的状态,第一次调用保存了一次callback属性,第二次调用覆盖了一次 callback属性。第二次将第一次覆盖掉了导致状态修改完后只调用了最后一次修改的,之前的那次保存作废了。

2、所以使用老的方式保存 callback 是不行的。那么怎么办? 将 callback属性 设置为 callbacks 并赋值一个空数组。在调用 then 方法后,如果状态是 pending 状态,则向 callbacks属性内 push 进去一个对象,对象内是那个时刻的 onResolve 和 onReject。这样多次调用 then 方法后,callbacks 属性内部 存放着多个对象,每个对象都是每一次调用 then 方法时的 onResolve 和 onReject

3、既然 callbacks 保存着多个对象,在执行的时候肯定要把每次的都执行一遍,所以在 Promise构造函数内部的 resolve 和 reject 方法内部调用的时候需要遍历 callbacks 数组。使用 forEach() 遍历,拿到每个对象,然后调用每个对象内对应的 resole 或 reject。

9、同步任务下 then 返回结果的实现

同步任务指的是:在执行器函数中,直接调用 resolve() 、reject() 函数 或者 使用 throw 方式改变状态。

then 方法返回的结果的特点:

  • 返回结果是由指定回调函数的执行结果决定的。如:返回的是非promise类型的数据,返回的结果就是成功的promise;返回的是promise对象,那返回的promise对象的结果就是 then方法返回的promise的结果和状态。
// 使用官方的 Promise
let p = new Promise((resolve, reject) => {
    resolve('ok');
})

let result = p.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})

console.log(result);

// result 就是 then方法 返回的 promise对象 PromiseState的值是 fulfilled,PromoseReuslt的值是 undefined。

修改我们自己的:

当使用Promise的代码是下面的代码:

let p = new Promie((resolve, reject) => {
    resolve('ok');
})

let result = p.then(value=>{
    // 1、返回非promise类型的其他类型数据
    // return 'hello promise';
    
    // 2、返回promise对象
    // return new Promise((resolve, reject) => {
    //	   resolve('ok');
	// });
    
    // 3、使用 throw 抛出异常
    throw '出错了~~~'
}, reason=>{})

先实现:成功状态实例化对象的 then方法 返回promise对象的结果:

Promise.prototype.then = function (onResolve, onReject) {
    // 1、既然需要返回一个Promsie对象,直接返回即可。
    return new Promise((resolve, reject) => {
        if (this.promiseState === 'fulfilled') {
            // 4、如果返回的是使用 throw 抛出异常,可以使用 try...catch... 来捕获这个异常
            try{
                /*
                 - 如果返回的结果是一个非promise的对象则返回的promise的状态是成功的
                 - 如果返回的结果是一个promise对象,则返回的promise对象的结果就是 then方法返回的promise的结果和状态
                */
                // 2、根据上述描述的特点,需要拿到回调函数的返回值
                let result = onResolve(this.promiseResult);
                // 3、判断 返回的是 promise对象 还是 非promise对象
                if (result instanceof Promsie){
                    // 既然返回的是 promise对象,那可以直接调用 then 方法
                    result.then(value=>{
                        // 因为then方法返回的promise的结果和状态和返回来的对象一样
                        // 所以直接调用对应的函数即可
                        resovle(value);
                    }, reason=>{
                        // 因为then方法返回的promise的结果和状态和返回来的对象一样
                        // 所以直接调用对应的函数即可
                        reject(reasono);
                    })
                }else{
                    // 非 promise的对象返回的 成功的状态, 所以直接调用 resovle函数即可。
                    resovle(result);
                }
            } catch(error){
                // 既然抛出了异常,直接返回失败的。所以调用 reject() 即可。
                reject(error);
            }
            
        };

        if (this.promiseState === 'rejected') {
            onReject(this.promiseResult);
        };

        if (this.promiseState === 'pending') {
            this.callbacks.push({
                onResolve: onResolve,
                onReject: onReject
            });
        };
        
    });
};


【解析:】
1、既然 then 返回的是一个 Promise 对象,那么直接在 then方法对应的函数内直接返回即可。(将直接的代码全部放入到执行器函数中,会直接同步执行的。)

2、本次完成的是 同步任务下成功状态实例化对象调用then方法返回promise对象的结果,所以现在判断状态等于fulfilled的语句内完成

3、代码注释中的第2点:是因为需要先获取回调函数的返回值,使用变量接受即可。

4、代码注释中的第3点:判断 返回的是 promise对象 还是 非promise对象。如果是 promise对象,直接调用该对象的 then 返回,成功就调用 resole() 函数,失败调用 reject(); 如果是非promise对象,则返回成功状态的,调用 resolve()。

5、代码注释中的第4点:也有可能返回的是使用 throw 抛出的异常,那么使用 try...catch... 即可捕获这个异常,因为是异常所以直接返回的是失败的状态,调用 reject()。



失败状态实例化对象的 then方法 返回promise对象的结果:

原理是一样的,就不单独解析了。

if (this.promiseState === 'rejected') {
    try {
        let result = onReject(this.promiseResult);
        if (result instanceof Promise) {
            result.then(value => {
                resovle(value);
            }, reason => {
                reject(reason);
            })
        } else {
            resovle(result);
        }
    } catch (error) {
        reject(error);
    }

};

10、异步任务修改then方法结果返回

异步任务使用promise

let p = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('ok');
    }, 2000);
})


let result = p.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})

console.log(result);


当打印result的时候,返回的promise状态还是pending。是因为异步任务还没有结束,所以没有修改状态呢。

异步任务下:实例对象返回【成功】状态,调用then方法返回的结果

// 因为是异步任务,所以主要焦点要聚焦在 添加then方法函数内部【判断状态是否为 pending】这一区域内。

// 添加 then() 方法
Promise.prototype.then = function (onResolve, onReject) {
    const self = this;

    return new Promise((resovle, reject) => {
        // 通过 if语句进行判断调用对应的回调函数
        if (this.promiseState === 'fulfilled') {
            // 此处省略
        };

        if (this.promiseState === 'rejected') {
            // 此处省略
        };

        // 1、【完成功能的代码要在这一块区域内】
        // 判断状态是否为 pending
        if (this.promiseState === 'pending') {
            this.callbacks.push({
                // 2、修改 onResolve对应的内容
                onResolve: function () {
                    // 这里使用 this.promiseResult 获取不到,所以顶部加入了const self = this;
                    // 获取返回结果
                    let result = onResolve(self.promiseResult);
                    // 判断返回结果的类型
                    if (result instanceof Promise) {
                        result.then(value => {
                            resovle(value);
                        }, reason => {
                            reject(reason);
                        })
                    } else {
                        resovle(result);
                    }
                },
                onReject: onReject
            });
        }
    });
} 



【解析:】

1、在第1点哪里,因为是异步任务,当执行到到console.log(result)的时候,上面的异步任务还没有结束,所以此时实例对象的状态为pending所以代码会取到第1点那里的区域,本次功能完成也是在这个区域内。

2、修改 onResolve对应的内容。之前 onResolve 对应的值 就是 onResolve 函数,然后在构造函数内部调用这个函数即可。那么我们可以先将 onResolve放入一个匿名函数内部,让在构造函数内部调用的是这个匿名函数,而这个匿名函数内部直接调用 onResolve 效果是一样的,而且还可以完成本次需要的功能。

3、在匿名函数内部直接调用 onResolve 函数,并将调用then方法的对象的结果值作为参数传入进去,由于使用 this 方法获取不到,可以使用之前使用过的 const self = this; 这个来获取到,将this.promiseResult 换成 self.promiseReuslt 就可以获取到了。

4、获取到调用 onResolve() 函数的结果保存到变量 result 中,对 result进行判断,是属于 非Promise对象类型的 还是 Promise对象类型。后面的操作就和同步时候的一样了。 如果是使用 throw 抛出的异常,那么则通过 try...catch... 进行捕获并做出相应的操作。

异步任务下:实例对象返回【失败】状态,调用then方法返回的结果

        // 判断状态是否为 pending
        if (this.promiseState === 'pending') {
            this.callbacks.push({
                onResolve: function () {
                    // pass
                    // 这里是:异步任务下:实例对象返回【成功】状态,调用then方法返回的结果的代码
                },
                onReject: function () {
                    // 如果没有返回结果是抛出异常使用 try...catch... 捕获
                    try {
                        // 这里使用 this.promiseResult 获取不到,所以顶部加入了const self = this;
                        // 获取返回结果
                        let result = onReject(self.promiseResult);
                        // 判断返回结果的类型
                        if (result instanceof Promise) {
                            result.then(value => {
                                resovle(value);
                            }, reason => {
                                reject(reason);
                            })
                        } else {
                            resovle(result);
                        }
                    } catch (error) {
                        reject(error);
                    }
                },
            });
        }

11、then 方法的优化

在 then 方法对应的函数中,无论是状态为成功、失败、待定,内部多有一套相同的代码:

try {
    let result = onResolve(self.promiseResult);
    if (result instanceof Promise) {
        result.then(value => {
            resovle(value);
        }, reason => {
            reject(reason);
        })
    } else {
        resovle(result);
    }
} catch (error) {
    reject(error);
}

既然上面的代码被重复使用,那么直接声明一个 callback 函数来封装这套代码,设置一个参数,该参数用于确定是 onResolve 或者 onReject

// 封装 callback 函数
function callback(type) {
    try {
        let result = type(self.promiseResult);
        if (result instanceof Promise) {
            result.then(value => {
                resovle(value);
            }, reason => {
                reject(reason);
            })
        } else {
            resovle(result);
        }
    } catch (error) {
        reject(error);
    }
};

优化后的代码如下:

Promise.prototype.then(function (onResolve, onReject) {
    const self = this;
    return new Promise((resolve, reject) => {
        // 封装 callback 函数
        function callback(type) {
            try{
                let result = type(self.promiseResult);
                if (result instanceof Promise) {
                    result.then(value=>{
                        resolve(value);
                    }, reason=>{
                        reject(reason);
                    })
                }else{
                    resolve(result);
                }
            } catch (error) {
                reject(error);
            }
        }
        
        
        // 状态为fulfilled 成功
        if (self.promiseState === 'fulfilled') {
            callback(onResolve);
        };
        
        // 状态为 rejected 失败
        if (self.promiseState === 'rejected') {
            callback(onReject);
        };
        
        // 状态为 pending 待定
        if (self.promiseState === 'pending') {
            self.callbacks.push({
                onResolve: function(){
                    callback(onResolve);
                },
                onReject: function(){
                    callback(onRejct);
                }
            })
        }
    });
})

12、封装catch方法,实现异常穿透与值传递

封装 catch 方法:

// 添加 catch() 方法
Promise.prototype.then = function (onReject) {
    this.then(undefined, onReject);
}


【解析:】
1、和 then 方法一样,通过Promise构造函数的prototype来添加一个 catch 方法

2catch 方法实现的功能其实就是 then 方法中失败的那个功能。

3、那么我们就可以直接在 catch 方法内部直接调用 then 方法就好了。因为 then 方法需要接受两个参数,而我们 catch 方法只有一个失败的形参,所以在使用then方法将第一个对应成功的形参设置为 undefined ,将失败的形参传入给 then 方法就好了。

使用自己写的Promise测试一下:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('Error')
    }, 2000)
});


p.then(value => {
    console.log('111');
}).then(value => {
    console.log('222');
}).then(value => {
    console.log('333');
}).catch(reason => {
    console.warn(reason);
})

【解析:】
会报错
其实报错的原因是,我们写的 then 方法并不支持只传入一个 onResolve应的函数。所以需要我们在 then 方法中将入一个判断,查看使用 then 方法是否只传入了一个 onResolve应的函数。

实现 catch 异常穿透:

// 添加 then() 方法
Promise.prototype.then = function (onResolve, onReject) {

    const self = this;

    // 在这里添加一个判断使用then方式是不是只传入一个值 【实现异常穿透】
    if (typeof onReject !== 'function') {
        onReject = (reason) => {
            throw reason;
        }
    }


    return new Promise((resovle, reject) => {
        function callback(type) {
            try {
                let result = type(self.promiseResult);
                if (result instanceof Promise) {
                    result.then(value => {
                        resovle(value);
                    }, reason => {
                        reject(reason);
                    })
                } else {
                    resovle(result);
                }
            } catch (error) {
                reject(error);
            }
        };
        
        if (this.promiseState === 'fulfilled') {
            callback(onResolve);
        };

        if (this.promiseState === 'rejected') {
            callback(onReject);
        };

        if (this.promiseState === 'pending') {
            this.callbacks.push({
                onResolve: function () {
                    callback(onResolve);
                },
                onReject: function () {
                    callback(onReject);
                },
            });
        }
    });

}



【解析:】
1、判断使用then方式是不是只传入一个值

2、如果 onReject 的类型不是一个函数,那么就证明传入了一个函数。我们就给 then 方法中的 onRejct 赋值一个新函数就好了。

3、赋值一个对应的函数,这个函数接收 reason ,然后将 reason 作为异常抛出去。

实现值传递:

在使用官方的 Promise 的时候,运行下面的代码不会出错,可以正常执行
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('ok')
    }, 2000)
});


p.then().then(value => {
    console.log('222');
}).then(value => {
    console.log('333');
}).catch(reason => {
    console.warn(reason);
})

【解析:】
第一个 then 方法没有传入任何值,后面的then方法依旧可以调用。

在我自己写的 Promise 运行上面的代码是不可以的,这里的原因和 异常穿透 是差不多的。解决办法也是一样,使用if语句进行判断。

// 添加 then() 方法
Promise.prototype.then = function (onResolve, onReject) {

    const self = this;

    // 在这里添加一个判断使用then方式是不是只传入一个值 【实现异常穿透】
    if (typeof onReject !== 'function') {
        onReject = (reason) => {
            throw reason;
        }
    }
    
    // 判断使用then方式是不是onResolve没有对应的值 【实现值传递】
    if (typeof onResolve !== 'function') {
        onResolve = value => value;
    }


    return new Promise((resovle, reject) => {
        function callback(type) {
            try {
                let result = type(self.promiseResult);
                if (result instanceof Promise) {
                    result.then(value => {
                        resovle(value);
                    }, reason => {
                        reject(reason);
                    })
                } else {
                    resovle(result);
                }
            } catch (error) {
                reject(error);
            }
        };
        
        if (this.promiseState === 'fulfilled') {
            callback(onResolve);
        };

        if (this.promiseState === 'rejected') {
            callback(onReject);
        };

        if (this.promiseState === 'pending') {
            this.callbacks.push({
                onResolve: function () {
                    callback(onResolve);
                },
                onReject: function () {
                    callback(onReject);
                },
            });
        }
    });
}

【解析:】
1、判断 onResovle 的类型是不是函数,如果不是函数,则证明使用 then 方法没有传入任何一个值。

2、value => value; 是箭头函数的简写,等同于 (value) => {return value}

3、在 onResolve 赋值一个函数,函数将值返回。这样值就可以传递给下一个 then 方法了。

13、封装 resolve 方法

作用:快速创建一个 promise 对象。

resolve 方法返回一个 Promise 对象。

  • 如果传入的值是非promoise对象,则返回状态为成功,结果值为传入值。
  • 如果传入的是promise对象,则返回的状态和结果值与传入的promise对象保持一致。
// 添加 resolve 方法
Promise.resolve = function (value) {
    return new Promise((resolve, reject) => {
        if (value instanceof Promise) {
            value.then(v => {
                resolve(v);
            }, r => {
                reject(r);
            })
        } else {
            resolve(value);
        }
    })
}

【解析:】
1、给 Promise 添加一个方法,这个方法和 then方法不同,不需要 prototype。

2、resolve方法接受一个参数。这里设置形参为 value

3、resolve方法返回一个promise对象,所以进来直接先return new Promiese(),主要功能代码在 return new Promiese() 内部完成。

4、判断传入的value是 非promise对象类型 还是 promise对象类型。

5、如果传入的值是非promoise对象,则返回状态为成功,结果值为传入值。

6、如果传入的是promise对象,则直接调用传入的该参数的then方法,根据状态走对应的函数。

使用 resolve 方法:

let p1 = Promise.resolve('ok');

let p2 = Promise.resolve(new Promise((resolve, reject) => {
    resolve('ok ok');
}));

let p3 = Promise.resolve(new Promise((resolve, reject) => {
    reject('no no');
}));

14、封装 reject 方法

作用:快速创建一个 promise 对象。

返回的结果永远都是一个失败的 promise 对象。

// 添加 reject 方法
Promise.reject = function (value) {
    return new Promise((resolve, reject) => {
        reject(value);
    })
}

【解析:】
1、其实和 resovle方法 差不多。

2、主要区别在于,reject方法返回的始终都是一个 rejected 状态的对象,所以只要在内部使用 Promise对象的执行器函数的reject() 将值传入进去即可。

使用 reject 方法

let p1 = Promise.reject('Error');

let p2 = Promise.reject(new Promise((resolve, reject) => {
    resolve('ok ok');
}));

let p3 = Promise.reject(new Promise((resolve, reject) => {
    reject('Error Error');
}));

15、封装 all 方法

返回一个 promise 对象,参数是一个数组。

结果由数组中的元素的状态决定:

  • 返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败就直接失败。

  • 成功的结果是所有 promise 对象的值组成的数组。

  • 失败的结果是数组中所有promise对象第一个状态为失败的promise对象的值。

// 添加 all 方法
Promise.all = function (promises) {
    // 返回一个 promise 对象
    return new Promise((resolve, reject) => {
        // 声明一个变量,存储成功的promise对象的个数
        let num = 0;

        // 声明一个变量,存储成功的promise对象的值
        let arr = [];

        for (let i = 0; i <= promises.length; i++) {
            // 通过每个promise对象的then方法来判断,
            // 当该对象是成功的状态,则会走进第一个函数,
            // 该对象是失败的状态,则会走进第二个函数。
            promises[i].then(value => {
                num += 1;
                arr[i] = value;
                // 判断num的数量和数组中全部promise对象的个数是否一样
                // 一样则证明全部都是成功的,返回所有对象结果值的数组。并且是成功状态的。
                if (num == promises.length) {
                    resolve(arr);
                }

            }, reason => {
                // 当遇到一个失败的时候就会走到这里。
                // 直接将这一个失败的结果值返回。
                reject(reason);
            })
        }
    })
}


【解析:】
1、all方法接受一个数组,数组内的元素都是promise对象(这里设置形参为promises)。返回一个 promise 对象,所以在 all方法函数内部直接返回一个 Promise 

2、在返回的 Promise 内部,声明变量 num 和 arr。 分别用于计数成功的 promise数量 和 对应的结果值。

3、使用 for 循环变量传入的数组类型的参数 promises。里面的元素都是 promise对象。

4、既然是 promise对象,肯定可以调用 then 方法,这样只要这个对象是成功的就会走 then 方法的第一个函数,如果是失败的就会走then 方法的第二个参数。

5、如果 promise 对象是成功的,则给 num 变量加1,并将这个对象的结果值放入 arr 变量对应的数组中(这里为什么使用 arr[i] = value 而不是 arr.push(value)。因为使用 push 方法会导致结果值顺序和传入的元素的顺序不一致,所以使用下标索引赋值)。当 num 的数量和 传入的promises 数组中的元素个数一直,这证明这个promises中的元素状态都是成功的,则返回成功的状态 和 结果值时候所有元素的结果值组成的数组。

6、如果 promise 对象是失败的,则直接调用 reject() 将该对象的结果值一并返回,状态也是失败的。

16、封装 race 方法

接受一个 Array 数组。

返回一个新的 promise,第一个完成的 promsie 的结果状态就是最终的结果状态。

// 添加 race 方法
Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i <= promises.length; i++) {
            promises[i].then(value => {
                resolve(value);
            }, reason => {
                reject(reason);
            })
        }
    })
}

【解析:】
1、race 方法接收一个数组,数组里面是 promise 对象。

2、race 方法返回一个数组,状态和结果值为接受数组内第一个元素的状态和结果值。

3、使用 for 循环遍历接受的数组,当遍历到第一个元素的的时候因为是promise对象,直接调用该元素的then方法,如果是改元素对象是成功状态的,走then第一个函数,如果是失败状态的走then第二个函数。

4、进入第一个函数 或 第二个函数 内,直接执行 resolve(value) 或 reject(reason) 将返回对应的结果值也返回。

17、then方法回调函数的异步执行

let p = new Promise((resolve, reject) => {
    resolve('ok');
    console.log('111');
})

p.then(value => {
    console.log('222');
}, reason => { })

console.log('333');

【解析:】
使用官方的Promise来运行上面的代码,输出的结果是 111  333  222

而用我们自己的Promise运行上面的代码,输出的结果是 111 222 333

需要我们在3处使用 setTimeout()

【第1处 和 第2处 是在 then 方法中,当状态为成功 或者 失败的时候。】
// 添加 then() 方法
Promise.prototype.then = function (onResolve, onReject) {
    const self = this;
    
    if (typeof onReject !== 'function') {
        onReject = (reason) => {
            throw reason;
        }
    }
    
    if (typeof onResolve !== 'function') {
        onResolve = value => value;
    }

    return new Promise((resovle, reject) => {
        function callback(type) {
            try {
                let result = type(self.promiseResult);
                if (result instanceof Promise) {
                    result.then(value => {
                        resovle(value);
                    }, reason => {
                        reject(reason);
                    })
                } else {
                    resovle(result);
                }
            } catch (error) {
                reject(error);
            }
        };
		
        // 判断状态为 成功
        if (this.promiseState === 'fulfilled') {
            // 第一处需要使用 setTimeout
            setTimeout(() => {
                callback(onResolve);
            });

        };
		
        // 判断状态为 失败
        if (this.promiseState === 'rejected') {
            // 第二处需要使用 setTimeout
            setTimeout(() => {
                callback(onReject);
            });
        };
		
        // 判断状态为 待定
        if (this.promiseState === 'pending') {
            this.callbacks.push(
                {
                    onResolve: function () {
                        callback(onResolve);
                    },
                    onReject: function () {
                        callback(onReject);
                    },
                }
            );
        }
    });

}

第三处是在Promsie构造函数内部的 resolve 和 reject 里面执行异步任务的回调函数处

function Promise(executor) {
    // 添加属性
    this.promiseState = 'pending';
    this.promiseResult = null;
    this.callbacks = [];
    
    const self = this;
    
    function resolve(data) {
        if (self.promiseState !== 'pending') return;
        self.promiseState = 'fulfilled';
        self.promiseResult = data;
        // 使用 setTimeout
        setTimeout(() => {
            self.callbacks.forEach(item => {
                item.onResolve(data);
            })
        })
    };

    function reject(data) {
        if (self.promiseState !== 'pending') return;
        self.promiseState = 'rejected';
        self.promiseResult = data;
        // 使用 setTimeout
        setTimeout(() => {
            self.callbacks.forEach(item => {
                item.onReject(data);
            })
        })
    };

    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }

}

18、class 版本的自定义封装 Promise

// class 版本
class Promise {
    // 构造方法
    constructor(executor) {
        // 添加属性
        this.promiseState = 'pending';
        this.promiseResult = null;
        // 添加 callback 属性
        this.callbacks = [];

        // 保存实例对象的 this值
        const self = this;

        // resolve 函数
        function resolve(data) {
            // 通过判断来设定,状态只能修改一次
            if (self.promiseState !== 'pending') return;

            // 1、修改对象的状态(promiseState)
            self.promiseState = 'fulfilled';    // resolvetd
            // 2、设置对象结果值(promiseResult)
            self.promiseResult = data;

            // 执行异步任务的回调函数
            setTimeout(() => {
                self.callbacks.forEach(item => {
                    item.onResolve(data);
                })
            })

        };

        // reject 函数
        function reject(data) {
            // 通过判断来设定,状态只能修改一次
            if (self.promiseState !== 'pending') return;

            // 1、修改对象的状态(promiseState)
            self.promiseState = 'rejected';
            // 2、设置对象结果值(promiseResult)
            self.promiseResult = data;

            // 执行异步任务的回调函数
            setTimeout(() => {
                self.callbacks.forEach(item => {
                    item.onReject(data);
                })
            })

        };

        try {
            // 同步执行 [执行器函数]
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    };

    // then 方法
    then(onResolve, onReject) {

        const self = this;

        // 判断使用then方式是不是只传入一个值
        if (typeof onReject !== 'function') {
            onReject = (reason) => {
                throw reason;
            }
        }

        // 实现值传递
        if (typeof onResolve !== 'function') {
            onResolve = value => value;
        }

        // 既然 then 方法需要返回一个 promise 对象,则直接返回即可
        return new Promise((resovle, reject) => {

            // 封装 callback 函数
            function callback(type) {
                try {
                    let result = type(self.promiseResult);
                    if (result instanceof Promise) {
                        result.then(value => {
                            resovle(value);
                        }, reason => {
                            reject(reason);
                        })
                    } else {
                        resovle(result);
                    }
                } catch (error) {
                    reject(error);
                }
            };

            // 通过 if语句进行判断调用对应的回调函数
            if (this.promiseState === 'fulfilled') {
                setTimeout(() => {
                    callback(onResolve);
                });

            };

            if (this.promiseState === 'rejected') {
                setTimeout(() => {
                    callback(onReject);
                });
            };

            // 判断状态是否为 pending
            if (this.promiseState === 'pending') {
                this.callbacks.push({
                    onResolve: function () {
                        callback(onResolve);
                    },
                    onReject: function () {
                        callback(onReject);
                    },
                });
            }
        });

    };

    // catch 方法
    catch(onReject) {
        this.then(undefined, onReject);
    };
	
    
    // 静态方法 resolve方法
    static resolve(value) {
        return new Promise((resolve, reject) => {
            if (value instanceof Promise) {
                value.then(v => {
                    resolve(v);
                }, r => {
                    reject(r);
                })
            } else {
                resolve(value);
            }
        })
    }

    // 静态方法 reject方法
    static reject(value) {
        return new Promise((resolve, reject) => {
            reject(value);
        })
    }

    // 静态方法 all 方法
    static all(promises) {
        // 返回一个 promise 对象
        return new Promise((resolve, reject) => {
            // 声明一个变量,存储成功的promise对象的个数
            let num = 0;

            // 声明一个变量,存储成功的promise对象的值
            let arr = [];

            for (let i = 0; i <= promises.length; i++) {
                // 通过每个promise对象的then方法来判断,
                // 当该对象是成功的状态,则会走进第一个函数,
                // 该对象是失败的状态,则会走进第二个函数。
                promises[i].then(value => {
                    num += 1;
                    arr[i] = value;
                    // 判断num的数量和数组中全部promise对象的个数是否一样
                    // 一样则证明全部都是成功的,返回所有对象结果值的数组。并且是成功状态的。
                    if (num == promises.length) {
                        resolve(arr);
                    }

                }, reason => {
                    // 当遇到一个失败的时候就会走到这里。
                    // 直接将这一个失败的结果值返回。
                    reject(reason);
                })
            }
        })

    }

    // 静态方法 rece 方法
    static race(promises) {
        return new Promise((resolve, reject) => {
            for (let i = 0; i <= promises.length; i++) {
                promises[i].then(value => {
                    resolve(value);
                }, reason => {
                    reject(reason);
                })
            }
        })
    }
}

六、async 与 await

1、async 函数

  • 函数的返回值为promise对象
  • promise对象的结果由 async 函数执行的返回值决定
async function main(){
    // 1、返回非promise对象的类型
    return '123';
    
    // 2、返回一个promise对象
    return new Promise((resovle, reject) => {
        resolve('ok');
        // reject('no');
    });
    
    // 3、抛出异常
    throw 'oh no no'
}

let result = main();
console.log(result);


【解析:】
1、当 async 函数返回一个非promise类型的对象,最后返回的promise对象的状态为成功,结果值为返回的数据。

2、当 async 函数返回一个promise对象,最后返回的promise对象的状态和结果值跟该对象的状态和结果值一致。

3、当 asnyc 抛出一个异常,则返回的promise对象的状态为失败,结果值为抛出的异常信息。

整体和Promise的then 方法类似。

2、await 表达式

  • await 右侧的表达式一般为promise对象,但也可以是其他的值。
  • 如果表达式是promise对象,await 返回的是promise的状态和结果值
  • 如果表达式是其他值,直接将此值作为 await 的返回值

注意:

  • await 必须写在 async 函数中,但 async 函数中可以没有 await
  • 如果 await 的 promise 失败了,就会抛出异常,需要通过 tyr..catch... 捕获处理
async function main(){
    let p1 = new Promise((resolve, reject) => {
        resolve('ok');
    });
    
    let p2 = new Promise((resolve, reject) => {
        reject('Error message');
    });
    
    // 1、await 表达式右侧为 非promise类型
    let res1 = await '123';
    console.log(res1);	// '123'
    
    // 2、await 表达式右侧为 promise类型 [成功状态]
    let res2 = await p1;
    console.log(res2);	// 'ok'
    
    // 3、await 表达式右侧为 promise类型 [失败状态]
    try{
        let res3 = await p2;
    }catch(e){
        console.log(e);	// 'Error message'
    }
   
}

main();

【解析:】
1await 表达式右侧为 非promise类型,则直接返回右侧的原数据。

2await 表达式右侧为 promise类型 [成功状态],则直接返回promise对象的结果值。

3await 表达式右侧为 promise类型 [失败状态],则会抛出异常,使用 try...catch... 来接受异常。

3、async 和 awiat 实践

// 引入 fs 模块
const fs = require('fs');

// 引入 util 模块
const util = require('util');
// 使用util的promisify,将 api转换为promise形态的函数 
// 【使用minReadFile就可以返回一个promise对象了】
const minReadFile = util.promisify(fs.readFile);

async function main() {
    try {
        let data1 = await minReadFile('./resource/1.txt');
        let data2 = await minReadFile('./resource/2.txt');
        let data3 = await minReadFile('./resource/3.txt');

        console.log(data1 + data2 + data3);
    } catch (error) {
        console.log(error);
    }

}

main();

4、async 和 awiat 发送ajax

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
</head>

<body>
    <div class="container">
        <button>发送ajax</button>
    </div>

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

        let btn = document.getElementsByTagName('button')[0];

        btn.addEventListener('click', async function () {
            try {
                let duanzi = await sendAjax('https://api.apiopen.top/getJoke');
                console.log(duanzi)
            } catch (error) {
                console.log(error);
            }

        })

    </script>
</body>

</html>

面试题

Promise的优点:

1、支持链式调用,解决了回调地狱问题。【必须说出来】
2、指定回调函数的方式更加灵活