promise me,一定要学会手写一个Promise(一)

134 阅读9分钟

Promise的介绍

1. Promise的介绍

Promise是es6引入的异步编程的解决方法,从语法上说,Promise就是一个构造函数。从功能上说,Promise就是封装了一个异步操作,并可以获取其成功或失败的结果。

2. 常见的异步操作

  1. fs文件读取:require("fs").readFile('index.html',(err,data)=>{})
  2. 数据库操作
  3. ajax请求:$.get(url,()=>{})
  4. setTimeout(()=>{},time)
  5. setInterval(()=>{},time)

3. Promise的优势:

  1. 指定回调函数的方式更加灵活 - 旧:必须在启动异步任务前指定 - promise:启动异步任务->返回promise对象-给promise对象绑定指定回调函数(甚至可以在异步任务结束后指定多个)
  2. 支持链式调用,可以解决回调地狱问题 - 回调地狱:回调函数嵌套使用,外部回调函数异步执行的结果是嵌套的回调执行的条件 - 不便于阅读、不便于异常处理。 - 可以通过promise链式调用解决。 -

4. Promise对象

const promise = new Promise((resolve,reject)=>{
	console.log(111)
});
console.log(promise);
/*
Promise {<pending>}
	[[Prototype]]: Promise
	[[PromiseState]]: "pending"
	[[PromiseResult]]: undefined
*/
  1. promise的状态
  • 实例对象中的一个属性,【PromiseState】
    • pending---未决定的
    • resolved/fullfilled---成功
    • rejected---失败
    • 状态只能从pending变为resolved/rejected,不能从resolved->rejected,rejected->resolved
  • 状态只能改变一次
  1. promise 对象的值
  • 实例对象中的另一个属性,【PromiseResult】,保存着对象[成功/失败]的结果。
  • resolve
  • reject

5. promise的工作流程

  • 通过new Promise()创建一个promise对象,在promise内部封装异步操作。
  • 如果异步操作成功,则调用resolve方法,并将promise的状态设置为resolved,并调用then()的第一个回调。返回一个新的promise对象。
  • 如果异步操作失败,则调用reject方法,并将promise的状态设置为rejected,并调用then()的第二个回调。返回一个新的promise对象。

6. promise的API

let p = new Promise((resolve,reject)=>{
      console.log(111);
})
 console.log(222);
 ---先输出111后输出222
  • Promise的构造函数:Promise(executor), ----executor函数:(resolve,reject)=>{@} ----resolve为成功的回调,reject为失败的回调 ----executor会在Promise内部立即同步执行,异步操作在执行器中执行。
  • Promise.prototype.then((onResolved,onReject)=>{}),分别是成功和失败的回调
  • Promise.prototype.catch(reson=>{}):失败的回调
  • Promise.resolve方法:(value)=>{},返回成功/失败的promise对象
  • Promise.reject方法:返回一个失败的promise对象
  • Promise.all:(promises)=>{},参数是一个数组,只有所有promise都成功才成功。只要有一个失败了就直接失败。
  • Promise.race:(promises)=>{},参数是一个数组,第一个promise的状态绝对最后结果。

7. promise的几个关键问题

  • 指定回调的方法?-- then() 或 catch()
  • 修改对象的状态?调用resolve() 或 reject() 或 throw
  let promise = new Promise((resolve,reject)=>{resolve('ok')})
  let promise = new Promise((resolve,reject)=>{reject('fail')})
  let promise = new Promise((resolve,reject)=>{throw 'error'})
  • 能否执行多个回调?当promise改为对应状态时,都会调用
 let promise = new Promise((resolve,reject)=>{
 	reject('ok')
 })
 promise.then((resolve)=>{	
 	console.log("执行了-1")
 }).then((resolve)=>{
 	console.log("执行了-2")
 });
 // 先输出"执行了-1",然后输出"执行了-2"
  • 指定回调与改变状态的执行顺序
    -- 改变状态和指定回调的执行顺序,谁先谁后? 答:都有可能,正常情况是先指定回调函数在改变状态,但是也可以先改变状态再指定回调

-- 如何先改变状态再指定回调?

  1. 在执行器中直接调用resolve()/reject()
  2. 延长更长时间才调用then()

-- 什么时候才得到数据?

  1. 如果先指定回调,当状态改变时,回调函数就会被调用,得到数据
  2. 如果先改变状态,那当指定回调时,回调函数就会被调用,得到数据

Promise初体验

1. 初体验--抽奖按钮

<!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>
    <button id="btn">抽奖按钮</button>
    <script>
        function rand(m, n) {
            return Math.ceil(Math.random() * (n - m + 1) + m - 1);
        }
        const btn = document.getElementById("btn");
        // 1. 通过setTimeout实现
        /*btn.addEventListener("click", function () {
             setTimeout(() => {
                 if (n < 30) {
                     console.log("恭喜你中奖了", n);
                 } else {
                     console.log("再接再厉!", n);
                 }
             }, 2000);
        })*/
        // 2. 通过Promise实现
        btn.addEventListener("click", function () {
            const promise = new Promise((resolve, reject) => {
                setTimeout(() => {
                    let n = rand(1, 100);
                    if (n < 30) {                      
                        resolve(n);// 将promise对象的状态设置为【成功】
                    } else {
                        reject(n);//将promise对象的状态设置为【失败】
                    }
                }, 2000);
            });
            promise.then(
                (n) => {//resolve
                    console.log("恭喜你中奖了", n);
                },
                (n) => {//reject
                    console.log("再接再厉!", n);
                });
        })

    </script>

</body>
</html>

2. 初体验--读取文件内容

// Promise初体验 - 读取文件内容
// node filename.js

const fs = require("fs");

// 1. 回调函数形式
// fs.readFile("./resource/readme.txt", (err, data) => {
//     if (err) throw err;
//     console.log(data.toString());
// });

// 2. Promise
let promise = new Promise((resolve, reject) => {
    fs.readFile("./resource/readme.txt", (err, data) => {
        if (err) {
            reject(err);
        } else {
            resolve(data);
        }
    });
})
promise.then(
    (data) => {//resolve
        console.log(data.toString());
    },
    (err) => {//reject
        throw err;
    }
);

3. 初体验--封装http请求

/*
    封装一个函数sendAjax 发送http请求
    参数:url,type
    返回:promise对象
*/
function sendAjax(url, type) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(type, 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('http://localhost:2000/server', 'get').then((
    data) => { console.log(data) },
    (err) => { console.log(err) }
);

4. 初体验--封装读取文件的方法

/*
    封装一个函数mineReadFile 读取文件内容
    参数:path-文件路径
    返回:promise对象
*/

function mineReadFile(path) {
    return new Promise((resolve, reject) => {
        // 读取文件
        require("fs").readFile(path, (err, data) => {
            // 判断是否读取成功
            if (err) reject(err);
            resolve(data);
        });
        // .then(
        //     (data) => {
        //         console.log(data)
        //     },
        //     (err) => {
        //         console.log(err);
        //     }
        // );
    })
}

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

Promise API

1. resolve方法

// 1. 传入的参数为非promise对象,则返回一个状态为成功的promise对象
// 2. 传入的参数为promise对象,则,参数的结果决定了resolve的结果
let promise1 = Promise.resolve(new Promise((resolve, reject) => {
    // resolve('success');
    reject('fail')
}));//传入:123
console.log(promise1);

2. reject方法

// 1. 传入的参数为非promise对象,则返回一个状态为失败的promise对象
// 2. 传入的参数为promise对象,则,参数的结果决定了reject的结果
let promise2 = Promise.reject(new Promise((resolve, reject) => {
    resolve('success');
}));//11
console.log(promise2);

3. all方法

// 参数:一个数组
// 返回值:所有Promise都成功,状态才是成功,只要有一个是失败,就为失败。
let arr = [
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('success');
        }, 1)
    }),
    new Promise((resolve, reject) => {
        // resolve('success');
        reject('fail');
    }),
    new Promise((resolve, reject) => {
        resolve('success');
    })];
let promise4 = Promise.all(arr);
console.log(promise4);

4. race方法

// 参数为数组,返回值为第一个promise的状态即为最终状态
let arr = [
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('success');
        }, 1)
    }),
    new Promise((resolve, reject) => {
        // resolve('success');
        reject('fail');
    }),
    new Promise((resolve, reject) => {
        resolve('success');
 })];


let promise3 = Promise.race(arr);
console.log(promise3);
// apply bind call

Promise中的一些重点

1. 通过then方法指定回调

指定回调和改变状态的执行顺序是不一定的喔,要视情况而定。指定回调是通过then方法传参(resolve,reject)来指定的,而状态的改变是在调用回调(resolve/reject)的那一刻。并且状态只能改变一次。

/*
Q:改变状态和指定回调的执行顺序,谁先谁后?
A:都有可能,正常情况是先指定回调函数再改变状态,但是也可以先改变状态再指定回调。
Q:如何先改变状态再指定回调?
A:1. 在执行器中直接调用resolve()/reject()
  2. 延长更长时间才调用then()
Q:什么时候才得到数据?
A:1. 如果先指定回调,当状态改变时,回调函数就会被调用,得到数据
  2. 如果先改变状态,那当指定回调时,回调函数就会被调用,得到数据
*/

// 1. 先改变状态,后指定回调
let promise1 = new Promise((resolve, reject) => {           
     resolve("1-resolve-ok");
     console.log("1-resolve-ok");           
});
promise1.then((resolve) => {
    console.log('2-then-resolve');
});

// 2. 先指定回调,后改变状态(注意是指定回调而不是执行回调)
let promise2 = new Promise((resolve, reject) => { 
    setTimeout(()=>{
        resolve("1-resolve-ok");
        console.log("1-resolve-ok");
    },1000);

});
promise2.then((resolve) => {
    console.log('2-then-resolve');
});

2. then方法的返回值

/*
then的返回结果,由then指定的回调函数执行的结果决定的
详细来说:
1. 如果抛出异常,新promise变为reject,reason为抛出的异常
2. 如果返回的是非promise的任意值,,新promise变为resolve,value为返回的值
3. 如果返回的是另一个新的promise,此promise的结果,就是新promise的结果
4. 默认状态'fullfilled',默认值undefined
*/
let promise = new Promise((resolve, reject) => {
    resolve('ok')
});
let result = promise.then(value => {
    console.log(value);
    /*
        1. 抛出异常
        throw 'error';//promise,reject,'error';
        result为:
        Promise
            [[Prototype]]: Promise
            [[PromiseState]]: "rejected"
            [[PromiseResult]]: "error"
    */
   // 
   /*
        2. 返回非promise的值
        return 123;
        result为:
        Promise {<pending>}
            [[Prototype]]: Promise
            [[PromiseState]]: "fulfilled"
            [[PromiseResult]]: 123
   */

  return new Promise((resolve,reject)=>{reject('error')});
  /*
    1. 返回Promise对象
    result为:
    Promise {<pending>}
        [[Prototype]]: Promise
        [[PromiseState]]: "rejected"
        [[PromiseResult]]: "error"
  */
}, reason => {
    console.log(reason);
})
console.log(result);// promise对象

3. Promise串联多个操作任务

/*
    promise的then方法返回一个promise,可以看成then的链式调用
    通过then的链式调用串联多个同步/异步任务
*/
let promise = new Promise((resolve, reject) => {
    //resolve('ok');//同步
    setTimeout(() => {//异步
        resolve('1-ok');
    }, 1000);
});
promise.then(value => {
    return new Promise((resolve, reject) => {
        resolve('2-ok');
    })
}).then(value => {
    console.log('3-ok', value);//2-ok
}).then(value => {
    console.log('4-ok', value);//undefined
});
console.log(promise);
/*
Promise {<pending>}
    [[Prototype]]: Promise
    [[PromiseState]]: "fulfilled"
    [[PromiseResult]]: "1-ok"
*/

4. 异常穿透

/*
    promise异常穿透
    1. 当使用promise的then链式调用时,可以在最后指定失败的回调
    2. 前面任何操作出了异常,都会传到最后失败的回调中处理

    中断promise链
    1. 当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
    2. 解决办法:在回调函数中,返回一个pedding状态的promise对象
*/
let promise = new Promise((resolve, reject) => {
    // setTimeout(() => {
        // reject('error');
    // }, 1000);
    throw 'error';
});
let result = promise.then(value => {
    console.log(111);
}, reason => {
    console.log(reason);
    return 456;
})//.then(value => {
//     console.log(222);
// }).then(value => {
//     console.log(333);
// })
.catch(reason => {
    console.log(reason);
    return 123;
}); // 按顺序执行,并最终进入到catch,不用每个then方法都写error的回调

console.log(result);

let promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error')
    }, 1000);
});
promise1.then(value => {
    console.log(111);
    return new Promise(() => { })// 返回pedding状态的promise对象,之后的then方法就不会执行了
}, reason => {
    console.log(reason);
    throw reason;
    return new Promise(() => { })
}).then(value => {
    console.log(222);
}).then(value => {
    console.log(333);
}).catch(reason => {
    console.log(reason);
});

5. async函数

/*
    1. async函数的返回值为一个promise对象
    2. 对象的值由函数的返回值由async函数的返回值决定
    3. async函数相当于promise的then方法
*/

async function main() {
    // 1. 返回非promise的值
    // return 123;
    /*
   res:
       Promise
           [[Prototype]]: Promise
           [[PromiseState]]: "fulfilled"
           [[PromiseResult]]: 123
   */

    // 2. 返回promise值
    return new Promise((resolve, reject) => {
        // resolve('ok');
        /*
            Promise {<pending>}
                [[Prototype]]: Promise
                [[PromiseState]]: "fulfilled"
                [[PromiseResult]]: "ok"
        */

        reject('error');
        /*
            Promise {<pending>}
                [[Prototype]]: Promise
                [[PromiseState]]: "rejected"
                [[PromiseResult]]: "error"

        */
       
        // 3. 抛出异常
        // throw 'oh no';
        /*
                Promise {<pending>}
                    [[Prototype]]: Promise
                    [[PromiseState]]: "rejected"
                    [[PromiseResult]]: 'oh no'

            */
    })


}
let res = main();
console.log(res)
/*
main函数没有返回值:
res:
    Promise
        [[Prototype]]: Promise
        [[PromiseState]]: "fulfilled"
        [[PromiseResult]]: undefined
*/

6. await表达式

/*
    1. await右侧的表达式一般为promise对象,但也可以为其他值
    2. 如果是promise对象,则await返回的是promise成功的值
    3. 如果表达式的是其他值,直接将此值作为await的返回值
    注意:
        1. await必须写在async函数中,但是async函数中可以没有await
        2. 如果await的promise失败了,就会抛出异常,需要通过try...catch捕获处理
*/
async function main() {
    let p = new Promise((resolve, reject) => {
        // resolve('ok');
        reject('error');
    });

    // 1. 非promise
    let res1 = await 123;
    console.log(res1);//123

    // 2. 为promise -成功
    // let res = await p;
    // console.log(res);//ok

    // 3. 为promise -失败
    try {
        let res2 = await p;
    } catch (e) {
        console.log(e);//error
    }
}

main();

7. async和await的实践--读取文件内容

// node filename.js
const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile)

// 读取文件
// fs.readFile('./resource/readme.txt', (err1, data1) => {
//     if (err1) throw err1;
//     fs.readFile('./resource/txt.txt', (err2, dada2) => {
//         if (err2) throw err2;
//         console.log(data1 + dada2);
//     })
// });

async function main() {
    try {
        let data1 = await mineReadFile('./resource/readme.xt');
        let data2 = await mineReadFile('./resource/txt.txt');

        console.log(data1 + data2);
    } catch (e) {
        console.log(e.code);
    }
}
main();

8. async和await的实践--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>async与await</title>
</head>

<body>
    <button id="btn">点击发送</button>
    <script>
        function sendAjax(url, type) {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.open(type, 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('http://localhost:2000/server', 'get').then((
        //     data) => { console.log(data) },
        //     (err) => { console.log(err) }
        // );
        const btn = document.getElementById('btn');
        btn.addEventListener('click', async function () {
            // 发送
            let res = await sendAjax('http://localhost:2000/server', 'get');
            console.log(res);
        })
    </script>
</body>

</html>