Promise基础讲解以及手写Promise

130 阅读17分钟

promise

是什么?

是ES6引入的进行异步编程的新的解决方案,从语法上来说是一个构造函数,可以封装异步的任务且可以对结果进行处理。

旧方案:单纯使用回调函数

异步编程有哪些:

  • fs 文件操作
require('fs').readFile('./index.html',(err,data)=>{})
  • 数据库操作
  • AJAX 网络请求
$.get('/server',(data)=>{})
  • 定时器 setTimeout
setTimeout(()=>{},2000);

为什么要使用Promise?

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

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

asyncFunc1(opt,(...args1) => {
    asyncFunc2(opt,(...args2) => {
        asyncFunc3(opt,(...args3) => {
            asyncFunc4(opt,(...args4) => {
                // some operation
            })
        })
    })
})

回调地狱的缺点:不便于阅读;不便于进行异常处理

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

  • 传统的必须在启动异步任务前指定回调函数
  • promise:启动异步任务=>返回promise对象=>给promise对象绑定回调函数(多个)

怎么用?

Promise初体验

<div class="app">
       <h2>Promise 初体验</h2>
       <button class="btn" id="btn">点击抽奖</button>
    </div>
    <script>
        // 生成随机数
        function rand(m,n){
            return Math.ceil(Math.random() * (n-m+1)) + m - 1;
        }

        // 获取元素对象
        const btn = document.querySelector("#btn");
        // 绑定点击事件
        btn.addEventListener('click',function(){
            // // 定时器
            // setTimeout(()=>{
            //     // 30%的中奖概率
            //     let n = rand(1,100);
            //     if (n <= 30) {
            //         alert("恭喜,10元");
            //     }else{
            //         alert('谢谢惠顾');
            //     }
            // })

            // Promise形式实现(构造函数:接收一个参数为函数)
            // resolve 解决   函数类型的数据
            // reject  拒绝   函数类型的数据
            const p = new Promise((resolve,reject) => {
                setTimeout(()=>{
                    // 30%的中奖概率
                    let n = rand(1,100);
                    if (n <= 30) {
                        resolve(n);   // 将promise对象的状态设置为【成功】,并传递结果
                    }else{
                        reject(n)     // 将promise对象的状态设置为【失败】
                        
                    }
                })
            })

            // 调用then方法
            p.then((value) => {
                // 为对象状态成功的回调
                alert("恭喜,10元,号码为" + res);
            },(reason) => {
                // 为对象状态失败的回调
                alert('谢谢惠顾,号码为' + res);
            });
        })

    </script>

Promise实践练习

fs模块
// 普通写法
const fs = require('fs');
fs.readFile('E:\\test.txt',(err,data) => {
    // 出错:直接抛出错误
    if(err) throw err;
    // 输出文件内容
    console.log(data.toString());
})


// Promise写法
let p = new Promise((resolve,reject) => {
    fs.readFile('E:\\test.txt',(err,data) => {
        // 出错
        if(err) reject(err);
        //成功
        resolve(data);
    })
})
// 调用then
p.then(value => {
    console.log(value.toString());
},reason => {
    console.log(reason);
})
AJAX请求
<div class="app">
    <button id="btn" class="btn">发送ajax请求</button>
</div>

<script>
    // 获取元素接口
    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) {
                    if (xhr.status >= 200 && xhr.status < 300) {
                        // 控制台输出响应体
                        resolve(xhr.response)
                    } else {
                        // 控制台输出响应码
                        reject(xhr.status)
                    }
                }
            }
        });
        // 调用then方法对成功和失败的结果进行处理
        p.then(value => {
            console.log(value)
        },reason => {
            console.warn(reason)
        })

        // 优点:1.指定回调函数更加灵活;2.支持链式调用,解决地狱回调问题
    })
</script>

Promise封装练习

fs文件读取操作
/*
封装一个函数,mineReadFile 读取文件内容
参数: path 文件路径
返回: promise 对象
*/ 
function mineReadFile(path) {
    return new Promise((resolve,reject) => {
        // 读取文件
        require('fs').readFile(path,(err,data) => {
            if (err) reject(err);
            // 成功
            resolve(data);
        })
    })
}
// 不需要在原始的readFile中写回调函数
mineReadFile('E:\\test.txt')
.then(value=>{
    // 输出文件内容
    console.log(value.toString());
},reason => {
    console.log(reason);
});
AJAX请求
/*
    封装一个函数sendAJAX发送GET AJAX请求
    参数  URL
    返回结果 Promise对象
*/
function sendAJAX(url){
    return new Promise((resolve,reject) => {
        // XMLHttpRequest:通过HTTP在浏览器和web服务器之间收发XML或其它数据
        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)
                }
            }
        }
    })
}

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

util.promisify(original)

一个错误优先的回调

/*
util.promisify 方法
*/
// 引入 util模块
const util = require('util');
// 引入fs模块
const fs = require('fs');
// 返回一个新函数
// 使用 util.promisify 函数将 fs.readFile 函数的回调方式转换为返回 Promise 的方式。
// 这意味着当你调用 mineReadFile 函数时,它将返回一个 Promise,而不是使用回调函数。
let mineReadFile = util.promisify(fs.readFile);
// 因为返回的是一个Promise,所以可以直接使用.then
mineReadFile('E:\\test.txt').then(value => {
    console.log(value.toString());
})

Promise对象状态属性介绍

Promise的状态

实例对象中的一个属性 【PromiseState】

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

Promise的对象的值

实例对象中的另一个属性 【PromiseResult】

对象【成功/失败】的结果

  • resolve
  • reject

1.pending变为resolved

2.pending变为rejected

Promise工作流程

image.png

Promise的API

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

promises:包含n个promise的数组

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

let p1 = new Promise((resolve,reject) => {
    resolve('ok');
})
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('ok');    // 只返回成功的结果

const result = Promise.all([p1,p2,p3])
console.log(result);  // [[PromiseState]]:"fulfilled"

let p4 = Promise.reject('err');  // 只返回失败的结果
const result2 = Promise.all([p1, p2, p3,p4])
console.log(result2);   // [[PromiseState]]:"rejected"
// [[PromiseResult]]: "err" 结果为失败的那个promise的值

Promise.race方法:(promises) => {}

promises:包含n个promise的数组

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

let p1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('ok');
    },1000)
})
let p2 = Promise.resolve('success');  // 第一个完成
let p3 = Promise.resolve('okkkk');   

const result = Promise.race([p1,p2,p3])
console.log(result);  // [[PromiseResult]]: "success"

Promise的几个关键问题

1. 如何改变对象的状态

let p = new Promise((resolve,reject) => {
    // 1. resolve函数
    resolve("ok");  // pending => fulfilled (resolved)
    // 2.reject 函数
    reject("error");  // pending => rejected
    // 3. 抛出错误
    throw '出问题了';  //  pending => rejected
});
console.log(p);  

2. 能否指定多个回调

当promise改变为对应状态时都会调用

let p = new Promise((resolve,reject) => {
    resolve("ok");  // 成功,以下指定的回调都会执行
    // 如果将以上成功回调注释掉,promise状态为pending,以下回调不会执行。
});
// 指定回调1
p.then(value => {
    console.log(value);
}) 
// 指定回调2
p.then(value => {
    alert(value);
}) 

3. 改变Promise状态和指定回调函数谁先谁后

都有可能

// 1.resolve()改变状态先执行,then()后执行:当(resolve,reject) => {}执行器当中的任务是同步任务时,直接调用resolve("ok");
// 就是先改变promise的状态,然后再执行回调
let p1 = new Promise((resolve, reject) => {
    resolve("ok");
});
// 2.then()先执行,resolve()改变状态后执行:当执行器当中的任务是异步任务时
let p2 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve("ok");
    },1000)
});

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

})

什么时候才能得到数据?(回调函数什么时候执行)

  1. 先指定回调的情况下(使用异步编程):状态发生改变时,回调函数才会调用,得到数据
  2. 先改变状态,当指定回调(给实例身上绑事件)时,回调函数就会调用。

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

  1. 如果抛出异常,新promise变为rejected,reason为抛出的异常
  2. 如果返回的是非promise的任意值,新promise变为resolved,value为返回的值
  3. 如果返回的是另一个新promise,此promise的结果就会成为新promise的结果
let p = new Promise((resolve, reject) => {
    resolve("ok");
});

let result = p.then(value => {
    // 以下的返回值决定result的结果
    // console.log(value);
    // // 抛出错误
    // throw "error";
    // // 2.返回结果为非promise类型的对象
    // return 111;
    // 3.返回结果是Promise对象
    return new Promise((resolve,reject) => {
        resolve('success')
    })
},reason => {
    console.warn(reason);
})

console.log(result);

5.promise如何串连多个操作任务?

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

p.then(value => {
    return new Promise((resolve,reject) => {
        resolve("success");
    });
}).then(value => {
    // then的返回结果为promise,状态由它指定的回调函数的返回值决定
    // 此回调函数的返回值没写,为undefined
    console.log(value);    // success
}).then(value => { 
    // 输出上面回调的结果 
    console.log(value);  // undefined
})

6.promise异常穿透

什么是异常穿透:当使用promise的then进行链式调用时,可以在最后指定失败的回调,前面的任何操作出了异常,都会传到最后失败的回调中处理

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

p.then(value => {
    throw '失败了!';
}).then(value => {
    console.log(222); 
}).then(value => { 
    console.log(333);
}).catch(reason => {
    // 只需要在最后指定一个失败的回调,就可以处理错误的结果
    console.log(reason);    // 输出'失败了'
})

7.如何中断promise链

返回pending状态的promise对象

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

p.then(value => {
    console.log(111); 
    // 有且只有一种方式
    return new Promise(() => {})   // pengding:状态未改变,以下回调无法执行
}).then(value => {
    console.log(222); 
}).catch(reason => {
    console.log(reason);
})

Promise自定义封装

1.初始结构搭建

image.png

2.resolve和reject结构搭建及代码实现

<promise.html>
<script src="./Promise.js"></script>
<script>
let p = new Promise((resolve,reject) => {
    // resolve('ok');
    reject('error');
})

console.log(p)
</script>

<promise.js>
// 声明构造函数
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;

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

    // resolve函数
    function resolve(data){
        // 1. 修改对象的状态(promiseState)
        // 这里的this改变的不是实例对象,而是window对象
        // this.PromiseState = 'fulfilled'
        self.PromiseState = 'fulfilled';
        //2. 设置对象结果值(promiseResult)
        self.PromiseResult = data
    }

    // reject函数
    function reject(data){
        // 1. 修改对象的状态(promiseState)
        self.PromiseState = 'rejected';
        //2. 设置对象结果值(promiseResult)
        self.PromiseResult = data
    }
    
    // 同步调用【执行器函数】
    executor(resolve,reject)
}

// 添加then方法
Promise.prototype.then = function(onResolved,onRejected){

}

3.throw抛出异常改变状态

<promise.html>
<script src="./Promise.js"></script>
<script>
    let p = new Promise((resolve,reject) => {
        throw "error";
    })

    console.log(p)
</script>

<promise.js>
// 在构造函数中修改如下代码

// throw抛出异常的情况下调用
try {
    // 同步调用【执行器函数】
    executor(resolve,reject)
} catch (error) {
    // 修改promise对象状态为【失败】
    reject();  // 直接调用reject()函数
}

4.确保promise状态只更改一次

在resolve和reject函数中判断状态,不为pending时直接返回

<promise.js>
// resolve函数
function resolve(data){
    // 判断状态
    if(self.PromiseState !== 'pending') return;
    self.PromiseState = 'fulfilled';
    self.PromiseResult = data
}

// reject函数
function reject(data){
    // 判断状态
    if(self.PromiseState !== 'pending') return;
    self.PromiseState = 'rejected';
    self.PromiseResult = data
}

5.then方法中执行回调

<promise.js>
// 添加then方法
Promise.prototype.then = function(onResolved,onRejected){
    // 调用回调函数  判断promiseState
    if(this.PromiseState === 'fulfilled'){
        // 把结果往回传
        onResolved(this.PromiseResult);
    }
    if(this.PromiseState === 'rejected'){
        onRejected(this.PromiseResult);
    }
}

6.异步任务回调的执行

问题:以下成功的回调不会执行,代码从上往下走setTimeout还未执行(需等待一秒),同步代码不会等待setTimeout执行,会先调用p.then,但到达then方法时p的状态仍处在pending,promise.js中的then方法中没有对pending状态的判断,所以没有返回。

<promise.html>
<script src="./Promise.js"></script>
<script>
    let p = new Promise((resolve,reject) => {
        setTimeout(() => {
            resolve('ok')
        },1000)
    })
    p.then(value => {
        console.log(value);  // 不会执行
    },reason => {
        console.log(reason);
    })
</script>

解决:在构造函数中添加一个对象属性(callback),用于存储回调函数,promise的then方法添加判断状态是否为pending,如果是就将回调函数添加到callback对象中,添加成功后,就可以在resolve函数中执行成功的回调,失败的回调同理。

<promise.js>
// 声明构造函数
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    // 声明属性
    this.callback = {};

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

    // resolve函数
    function resolve(data){
        // 判断状态
        if(self.PromiseState !== 'pending') return;
        // 1. 修改对象的状态(promiseState)
        self.PromiseState = 'fulfilled';
        //2. 设置对象结果值(promiseResult)
        self.PromiseResult = data

        // 调用成功的回调函数
        if(self.callback.onResolved){   // 如果回调当中有onResolved属性就执行它
            self.callback.onResolved(data);  // 参数别忘了
        }
    }

    // reject函数
    function reject(data){
        // 判断状态
        if(self.PromiseState !== 'pending') return;
        // 1. 修改对象的状态(promiseState)
        self.PromiseState = 'rejected';
        //2. 设置对象结果值(promiseResult)
        self.PromiseResult = data

        // 调用失败的回调函数
        if(self.callback.onRejected){   // 如果回调当中有onResolved属性就执行它
            self.callback.onRejected(data);  // 参数别忘了
        }
    }

    // throw抛出异常的情况下调用
    try {
        // 同步调用【执行器函数】
        executor(resolve,reject)
    } catch (error) {
        // 修改promise对象状态为【失败】
        reject();  // 直接调用reject()函数
    }
}

// 添加then方法
Promise.prototype.then = function(onResolved,onRejected){
    // 调用回调函数  判断promiseState
    if(this.PromiseState === 'fulfilled'){
        // 把结果往回传
        // onResolved();  // 不传结果为undefined
        onResolved(this.PromiseResult);
    }
    if(this.PromiseState === 'rejected'){
        onRejected(this.PromiseResult);
    }
    // 判断pending状态
    if(this.PromiseState === 'pending'){
        // 保存回调函数(保存在任务队列中,或叫微任务队列)
        // 将回调函数绑定在实例上
        this.callback = {
            onResolved : onResolved,
            onRejected : onRejected
        }
    }
}

7.指定多个回调的实现

例如如下代码,控制台不会输出东西,但会有弹框,回调函数被覆盖

<promise.html>
<script src="./Promise.js"></script>
<script>
    let p = new Promise((resolve,reject) => {
        setTimeout(() => {
            resolve('ok')
        },1000)
    })
    p.then(value => {
        console.log(value);
    },reason => {
        console.log(reason);
    })

    p.then(value => {
        alert(value);
    },reason =>{
        alert(reason);
    })
</script>

解决:保存所有回调

<promise.js>
// 1.将callback对象改为callbacks数组
this.callbacks = [];

// 2.将回调函数push到callbacks数组中
this.callbacks.push({
    onResolved : onResolved,
    onRejected : onRejected
})

// 3. 遍历执行成功回调函数(失败同理)
self.callbacks.forEach(item => {
    item.onResolved(data);
});

8.同步任务then返回结果

首先,p.then方法的返回结果应该是一个promise对象,以下代码返回undefined

<promise.html>
<script src="./Promise.js"></script>
<script>
    let p = new Promise((resolve,reject) => {
        resolve('ok')
    })

    const result = p.then(value => {
        console.log(value);
    },reason => {
        console.log(reason);
    })
    console.log(result)
</script>

既然如此,就返回一个promise对象,但状态一直处在pending

<promise.html>
<script src="./Promise.js"></script>
<script>
    let p = new Promise((resolve,reject) => {
        resolve('ok')
    })

    const result = p.then(value => {
        return 'hello';
    },reason => {
        console.log(reason);
    })
    console.log(result)
</script>

<promise.js>
Promise.prototype.then = function(onResolved,onRejected){
    return new Promise((resolve,reject) => {
        // 调用回调函数  判断promiseState
        if(this.PromiseState === 'fulfilled'){
            // 把结果往回传
            // onResolved();  // 不传结果为undefined
            onResolved(this.PromiseResult);
        }
        if(this.PromiseState === 'rejected'){
            onRejected(this.PromiseResult);
        }
        // 判断pending状态
        if(this.PromiseState === 'pending'){
            // 保存回调函数(保存在任务队列中,或叫微任务队列)
            // 将回调函数绑定在实例上
            this.callbacks.push({
                onResolved : onResolved,
                onRejected : onRejected
            })
        }
    })
}

需要根据回调函数的执行结果改变promise对象的状态,以及抛出异常的情况下返回的结果

<promise.js>
Promise.prototype.then = function(onResolved,onRejected){
    return new Promise((resolve,reject) => {
        // 调用回调函数  判断promiseState
        if(this.PromiseState === 'fulfilled'){
            try {
                // 获取回调函数的执行结果
                let result = onResolved(this.PromiseResult);
                // 判断
                if(result instanceof Promise){
                    // 如果是promise类型对象
                    result.then(v => {
                        resolve(v);
                    },r =>{
                        reject(v);
                    })
                }else{
                    // 结果的对象状态为 成功
                    resolve(result);
                }
            } catch (e) {
                reject(e);
            }
            
        }
        if(this.PromiseState === 'rejected'){
            onRejected(this.PromiseResult);
        }
        // 判断pending状态
        if(this.PromiseState === 'pending'){
            // 保存回调函数(保存在任务队列中,或叫微任务队列)
            // 将回调函数绑定在实例上
            this.callbacks.push({
                onResolved : onResolved,
                onRejected : onRejected
            })
        }
    })
}

9. 异步任务then返回结果

以下返回的状态为pending

<promise.html>
<script src="./Promise.js"></script>
<script>
    let p = new Promise((resolve,reject) => {
        setTimeout(() => {
            resolve('ok')
        },1000)
    })
    // 由于是异步任务,代码走到这里时promise为pending
    const res = p.then(value => {
        console.log(value);
    },reason => {
        console.warn(reason);
    })
    console.log(res)
</script>

所以promise.js中的代码会来到这里

image.png

需要对里面的代码进行改进

if(this.PromiseState === 'pending'){
    // 保存回调函数(保存在任务队列中,或叫微任务队列)
    // 将回调函数绑定在实例上
    this.callbacks.push({
        onResolved : function(){
            // 执行成功回调函数
            let result = onResolved(self.PromiseResult)
            // 判断
            if(result instanceof Promise){
                result.then(v => {
                    resolve(v);
                }, r=> {
                    reject(r);
                })
            }else{
                resolve(result);
            }
        },
        onRejected : function(){
            // 执行失败回调函数
            let result = onRejected(self.PromiseResult)
            // 判断
            if(result instanceof Promise){
                result.then(v => {
                    resolve(v);
                }, r=> {
                    reject(r);
                })
            }else{
                resolve(result);
            }
        }
    })
}

这个要注意的是当setTimeout里面执行的是reject时,PromiseState仍为"fulfilled",这个因为没有return返回的就是undefined,而undefined属于非promise,所以是成功的状态。

加上try catch解决抛出异常的问题

<promise.html>
<script src="./Promise.js"></script>
<script>
    let p = new Promise((resolve,reject) => {
        setTimeout(() => {
            reject('error')
        },1000)
    })
    // 由于是异步任务,代码走到这里时promise为pending
    const res = p.then(value => {
        return 'oh'
    },reason => {
        // console.warn(reason);
        throw "Error"
    })
    console.log(res)
</script>

<promise.js>
// 判断pending状态
if(this.PromiseState === 'pending'){
    // 保存回调函数(保存在任务队列中,或叫微任务队列)
    // 将回调函数绑定在实例上
    this.callbacks.push({
        onResolved : function(){
            try{
                // 执行成功回调函数
                let result = onResolved(self.PromiseResult)
                // 判断
                if(result instanceof Promise){
                    result.then(v => {
                        resolve(v);
                    }, r=> {
                        reject(r);
                    })
                }else{
                    resolve(result);
                }
            }catch(e){
                reject(e);
            }

        },
        onRejected : function(){
            try {
                // 执行失败回调函数
                let result = onRejected(self.PromiseResult)
                // 判断
                if(result instanceof Promise){
                    result.then(v => {
                        resolve(v);
                    }, r=> {
                        reject(r);
                    })
                }else{
                    resolve(result);
                }
            } catch (e) {
                reject(e);
            }
        }
    })
}

10. then方法完善与优化

同步任务调用reject回调情况下

if(this.PromiseState === 'rejected'){
    try {
        let result = onRejected(this.PromiseResult);
        if(result instanceof Promise){
            result.then(v => {
                resolve(v);
            },r => {
                reject(r);
            })
        }else{
            resolve(result);
        }
    } catch (e) {
        reject(e);
    }

}

以下重复代码太多,只有onRejected和onResolved的区别

try {
    let result = onRejected(this.PromiseResult);
    if(result instanceof Promise){
        result.then(v => {
            resolve(v);
        },r => {
            reject(r);
        })
    }else{
        resolve(result);
    }
} catch (e) {
    reject(e);
}

封装callback函数

Promise.prototype.then = function(onResolved,onRejected){
    const self = this;
    return new Promise((resolve,reject) => {
        function callback(type){
            try {
                // 获取回调函数的执行结果,这里要改变this的指向
                let result = type(self.PromiseResult);
                // 判断
                if(result instanceof Promise){
                    // 如果是promise类型对象
                    result.then(v => {
                        resolve(v);
                    },r =>{
                        reject(v);
                    })
                }else{
                    // 结果的对象状态为 成功
                    resolve(result);
                }
            } catch (e) {
                reject(e);
            }
        }

        // 调用回调函数  判断promiseState
        if(this.PromiseState === 'fulfilled'){
            callback(onResolved);            
        }

        if(this.PromiseState === 'rejected'){
            callback(onRejected);
        }
        // 判断pending状态
        if(this.PromiseState === 'pending'){
            // 保存回调函数(保存在任务队列中,或叫微任务队列)
            // 将回调函数绑定在实例上
            this.callbacks.push({
                onResolved : function(){
                    callback(onResolved);
                },
                onRejected : function(){
                    callback(onRejected);
                }
            })
        }
    })
}

11.封装catch方法和异常穿透

指定失败的回调函数

<promise.html>
<script src="./Promise.js"></script>
<script>
    let p = new Promise((resolve,reject) => {
        setTimeout(() => {
            reject('err')
        },1000)
    })
    p.then(value => {
        console.log(111);
        throw "Error";
    }).then(value => {
        console.log(222);
    }).then(value => {
        console.log(333);
    }).catch(reason => {
        console.warn(reason);
    })
</script>

promise.js添加处理错误的函数,在then中已经有处理错误的方法,直接调用

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

但直接使用以上catch方法会报“onRejected is not a function”的错误,原因是then后面只传递了成功的回调,失败的回调没有传递,需要在then方法接收参数后判断onRejected是否为function,不是则初始化。解决异常穿透的问题。

if(typeof onRejected !== 'function'){
    onRejected = reason => {
        throw reason;
    }
}

image.png

这样一来,then后面只传递成功的回调就不会报错,但then既不传递成功的回调时依然会报错,需要添加onResolved的判断

if(typeof onResolved !== 'function'){
    onResolved = value => value
}

12.Promise.resolve方法封装

直接调用以下代码会报“Promise.resolve is not a function”。(非自带Promise)

promise.html
<script src="./Promise.js"></script>
let p = Promise.resolve("ok");
console.log(p)

promise.js添加resolve方法

// 添加 resolve 方法  (属于函数对象,而不是实例对象)
Promise.resolve = function(value){
    // 返回Promise对象
    return new Promise((resolve,reject) => {
        if(value instanceof Promise){
            value.then(v =>{
                resolve(v);
            },r => {
                reject(r);
            })
        }else{
            // 状态设置为成功
            resolve(value);
        }
    })
}

promise.html

let p = Promise.resolve("ok");
let p2 = Promise.resolve(new Promise((resolve,reject) => {
    resolve("success")
}))
// 返回的promise的结果成功p2也成功
console.log(p2)  // fulfilled,success

13.Promise.reject方法封装

<promise.html>
<script src="./Promise.js"></script>
let p = Promise.resolve("ok");
let p2 = Promise.reject(new Promise((resolve,reject) => {
    resolve("success")
}))
// reject方法不管传入什么类型的值结果都是失败的
console.log(p2)  // rejected,Promise

<promise.js>
// 添加 reject 方法
Promise.resolve = function(reason){
    // 返回Promise对象
    return new Promise((resolve,reject) => {
        reject(reason);
    })
}

14.Promise.all方法封装

<promise.html>
<script src="./Promise.js"></script>
let p1 = Promise.resolve((resolve, reject) => {
    setTimeout(()=>{
        resolve("success")
    },1000)
});
let p2 = Promise.reject("yes")
let p3 = Promise.resolve("okkk")

let result = Promise.all([p1,p2,p3])
console.log(result)

<promise.js>
// 添加 all 方法
Promise.all = function(promises){
    return new Promise((resolve,reject) => {
        // 声明变量
        let count = 0; // 统计resolve的promise个数
        let arr = [];  // 存储执行成功的返回结果

        // 遍历数组
        for(let i=0;i<promises.length;i++){
            // 执行每一个promise
            promises[i].then(v  => {
                // 能进到这里的对象状态都是成功的
                count++;
                // 将当前对象成功的结果放入arr中,push()不能保证结果按顺序存放
                arr[i] = v;
                // 判断:全部的结果都是成功才能改变状态
                if(count === promises.length){
                    resolve(arr);
                }
            },r => {
                // 只要有一个执行失败就直接返回
                reject(r);
            })
        }
    })
}

15.Promise.race方法封装

<promise.html>
<script src="./Promise.js"></script>
let p1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve("success")
    },1000)
});
let p2 = Promise.resolve("yes")
let p3 = Promise.resolve("okkk")

let result = Promise.race([p1,p2,p3])
console.log(result)  // yes

<promise.js>
// 添加 race 方法
Promise.race = function(promises){
    return new Promise((resolve,reject) => {
        // 遍历数组
        for(let i=0;i<promises.length;i++){
            // 执行每一个promise
            console.log("11111",promises[i]);
            promises[i].then(v  => {
                resolve(v)
            },r => {
                reject(r);
            })
        }
    })
}

16.then方法回调的异步执行

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

// then方法里面的函数是异步微任务
p.then((result) => {
    console.log(222)
});
console.log(333)

以上代码正确执行顺序是111 333 222

但使用自定义封装的promise的执行顺序是111 222 333

我们需要对then方法做一些改进:在then方法中把调用回调函数放入异步任务中

image.png

async & await

async

  1. 函数的返回值为promise对象
<script>
    async function main(){};
    let result = main();
    console.log(result); // Promise
</script>
  1. promise对象的结果由async函数执行的返回值决定
async function main(){
    // 1.返回值为非promise对象:promise的状态为fulfilled,结果为123
    // return 123;
    // 2.返回值是一个Promise对象:由promise对象的结果决定main()的结果
    // return new Promise((resolve,reject) => {
    //     resolve('ok');
    // })
    // 3.抛出异常:promise的状态为失败,结果为a oh
    throw "a oh"
};
let result = main();
console.log(result);

await

  1. await右侧的表达式一般为promise对象,也可以是其它值
  2. 表达式为promise对象时,await返回的是promise成功的值,promise失败时需要通过try...catch..捕获处理
  3. 表达式为其它值时,直接将此值作为await的返回值
async function main(){
    let p = new Promise((resolve,reject) => {
        resolve('ok');
    })
    let p2 = new Promise((resolve, reject) => {
        reject('error');
    })
    // 1.右侧为promise的情况
    let res = await p;
    console.log(res);   // ok
    // 2.右侧为其它数据的情况
    let res2 = await 20;
    console.log(res2);  // 20
    // 3.promise是失败的状态
    try {
        let res3 = await p2;
    } catch (e) {
        console.log(e);  // error
    }
};

main()

async和await结合实践:拼接三个文件的内容

普通回调函数写法

const fs = require('fs')
fs.readFile('1.txt',(err,data1) => {
    if(err) throw err;
    fs.readFile('2.txt',(err,data2) => {
        if(err) throw err;
        fs.readFile('3.txt',(err,data3) => {
            if(err) throw err;
            console.log(data1+data2+data3);
        })
    })
})

async await写法:看不到回调函数,使代码看起来更简洁

const fs = require('fs')
const util = require('util');
const mineReadFile = util.promisify(fs.readFile); //将api转成promise型对象
async function main(){
    try {
        let data1 = await mineReadFile('./1.txt');
        let data2 = await mineReadFile('./1.txt');
        let data3 = await mineReadFile('./1.txt');
        console.log(data1 + data2 +data3);
    } catch (e) {
        console.log(e);
    }
}
main()

async与await结合发送AJAX请求

<button id="btn">点击获取信息</button>
<script>
    function sendAJAX(url) {
        return new Promise((resolve, reject) => {
            // XMLHttpRequest:通过HTTP在浏览器和web服务器之间收发XML或其它数据
            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)
                    }
                }
            }
        })
    }

    // 接口地址:http://localhost:3003/player/getPlayer2
    let btn = document.querySelector('#btn')
    btn.addEventListener('click',async function(){
        // 获取信息
        let mess = await sendAJAX('http://localhost:3003/player/getPlayer2');
        console.log(mess);
    })
</script>