Promise-课堂笔记

课程地址1

课程地址2

Promise 初体验

传统地使用回调进行异步操作,容易造成回调地狱的问题:

javascript 复制代码
require("fs").readFile("foo.txt", (err, data) => {});
$.get("/server", (data) => {});
setTimeout(() => {}, 200);

从语法上来说:Promise 是一个构造函数 ,它接受一个函数 作为参数,而这个函数的2个参数 又必须是函数,分别是 resolve 和 reject

从功能上来说:Promise 对象用于封装一个异步操作并可以获取其成功/失败的结果值,它的设计思想是,所有异步任务都返回一个 Promise 实例

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

javascript 复制代码
function f1(resolve, reject) {	// resolve, reject 都是函数
  // 异步代码...
}
var p1 = new Promise(f1);

Promise 实例有一个then方法,用来指定下一步的回调函数

javascript 复制代码
var p1 = new Promise(f1);
p1.then(f2);

Promise 对象有 3 种状态:

  • 异步操作未完成(pending)
  • 异步操作成功(fulfilled)
  • 异步操作失败(rejected)

then方法可以接受两个回调函数,第一个是异步操作成功时(变为fulfilled状态)的回调函数,第二个是异步操作失败(变为rejected)时的回调函数(该参数可以省略)。一旦 Promise 状态发生改变,就调用相应的回调函数

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>Promise 初体验</h2>
    <button id="btn">点击抽奖</button>
    <script>
        function rand(m, n) {
            return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
        }
        const btn = document.querySelector("#btn");
        btn.addEventListener("click", function() {
            const p = new Promise((resolve, reject) => {
            	// 异步任务
                setTimeout(() => {
                    let n = rand(1, 100);
                    if (n <= 30) {
                        resolve(n);     // 将成功状态返回
                    } else {
                        reject(n);      // 将失败状态返回
                    }
                }, 1000);
            });
            
            p.then(
                (value) => {     // resolve
                    alert("恭喜中奖: n = " + value);
                },
                (reason) => {     // reject
                    alert("再接再厉: n = " + reason);
                }
            );
        });
    </script>
</body>
</html>   

Promise实践练习

fs 读取文件

原始版本:

javascript 复制代码
const fs = require("fs");

let path = "foo.txt";

fs.readFile(path, (err, data) => {
    if (err) throw err;
    console.log(data.toString());
})

使用 Promise 封装异步任务:

javascript 复制代码
const fs = require("fs");

let path = "foo.txt";

let p = new Promise((resolve, reject) => {
    fs.readFile(path, (err, data) => {
        if (err) reject(err);
        else resolve(data.toString());
    });
});

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

其实异步任务这样写也可以:

javascript 复制代码
let p = new Promise((resolve, reject) => {
    fs.readFile(path, (err, data) => {
        if (err) reject(err);
        resolve(data);	// 没有 else
    });
});

虽然看起来如果 err 了,resolve 也会"错误地"执行,但事实上,由于 Promise 的状态只会被改变一次,所以如果 err 了下面的 resolve 是不会执行的

AJAX 请求

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h2>Promise 封装 ajax 请求</h2>
    <button class="btn">点击发送 ajax</button>
    <script>
        const btn = document.querySelector(".btn");
        btn.addEventListener("click", function () {
            const p = new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.open("GET", "https://www.mxnzp.com/api/jokes/list/random");
                xhr.send();
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4) {
                        if (xhr.status >= 200 && xhr.status < 300) {
                            resolve(xhr.response);
                        } else {
                            reject(xhr.status);
                        }
                    }
                }
            });
            p.then(
                value => console.log(value),
                reason => console.warn(reason)
            );
        });
    </script>
</body>
</html>

封装 readFile

手动封装

javascript 复制代码
function myReadFile(path) {
    return new Promise((resolve, reject) => {
        require("fs").readFile(path, (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

myReadFile("foo.txt").then(
    value => console.log(value.toString()),
    reason => console.log(reason)
);

使用 util.promisify 封装

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

javascript 复制代码
const util = require("util");
const fs = require("fs");

myReadFile = util.promisify(fs.readFile);

myReadFile("foo.txt").then(
    value => console.log(value.toString()),
    reason => console.log(reason)
);

封装 AJAX

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        function sendAJAX(url) {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.open("GET", url);
                xhr.send();
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4) {
                        if (xhr.status >= 200 && xhr.status < 300) {
                            resolve(xhr.response);
                        } else {
                            reject(xhr.status);
                        }
                    }
                }
            });
        }
        sendAJAX("https://www.mxnzp.com/api/jokes/list/random").then(
            value => console.log(value),
            reason => console.warn(reason)
        );
    </script>
</body>
</html>

PromiseState

Promise 状态:实例对象的一个属性PromiseState

这个状态只能改变一次

PromisResult

异步任务成功或失败的结果,也就是 then() 的2个函数的参数:value & reason

Promise 流程

Promise API

Promise 构造函数

javascript 复制代码
let executor = (resolve, reject) => {}	// 执行器
Promise(executor) {}

resolve 函数:内部定义成功时我们调用的函数 value => {}

reject 函数:内部定义失败时我们调用的函数 reason => {}

executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行

then

javascript 复制代码
let onResolved = (value) => {};		// 成功的回调
let onRejected = (reason) => {};	// 失败的回调
Promise.prototype.then(onResolved, onRejected);

then 会返回一个新的 Promise 对象

catch

只在失败时执行

javascript 复制代码
let onRejected = (reason) => {}
Promise.prototype.catch(onRejected)
html 复制代码
<body>
    <script>
        let p = new Promise((resolve, reject) => {
            reject("Error");
        });
        p.catch(
            reason => console.warn(reason),
        );
    </script>
</body>

resolve

这个方法属于 Promise 函数对象,并不属于实例对象

它返回一个成功/失败的 Promise 对象(快速封装得到一个 Promise 对象)

javascript 复制代码
PromiseConstructor.resolve<string>(value: string): Promise<string>
js 复制代码
// 如果传入的参数为非 Promise 类型的对象,则返回的结果为成功的 Promise 对象
let p1 = Promise.resolve(521);
console.log(p1);    // Promise { 521 }
p1.then(
    value => console.log(value),
);
// 如果传入的参数为 Promise 对象,则参数的结果决定了 resolve 的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
    resolve("OK");
}));
p2.then(
    value => console.log(value),
);

let p3 = Promise.resolve(new Promise((resolve, reject) => {
    reject("ERROR");
}));
p3.catch(
    reason => console.log(reason),
);

reject

这个方法也是属于 Promise 函数对象,并不属于实例对象

快速返回一个失败的 Promise 对象

javascript 复制代码
PromiseConstructor.reject<never>(reason?: any): Promise<never>
html 复制代码
<body>
    <script>
        // reject.js
        let p1 = Promise.reject(521);

        console.log(p1);
        p1.catch(
            reason => console.log(reason),
        );

        let p2 = Promise.reject(new Promise((resolve, reject) => {
            resolve("OK");  // fullfilled -> rejected
        }));
        console.log(p2);	// 一个失败的 Promise 对象,但是它的 PromiseResult 是上面 resolve("OK") 的成功的 Promise 对象
        p2.catch(
            reason => console.log(reason),
        );
    </script>
</body>

all

javascript 复制代码
Promise.all(promises)	// promises 是一个包含 n 个 promise 的数组

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

成功的结果是所有成功的 promises 结果组成的数组

失败的结果是 promises 数组中失败的 promise 的结果

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        let p1 = new Promise((resolve, reject) => {
            resolve("OK");
        });

        let p2 = Promise.resolve("Success");
        let p3 = Promise.resolve("Yes");

        const result = Promise.all([p1, p2, p3]);
        console.log(result);
    </script>
</body>
</html>

race

javascript 复制代码
Promise.race(promises)	// promises 是一个包含 n 个 promise 的数组

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

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        let p1 = new Promise((resolve, reject) => {
            resolve("OK");
        });

        let p2 = Promise.resolve("Success");
        let p3 = Promise.resolve("Yes");
        console.log(Promise.race([p1, p2, p3]));    // p1: OK
    </script>
</body>
</html>

Promise 关键问题

如何修改 Promise 对象的状态

以下3种方式可以修改 Promise 对象的状态:

javascript 复制代码
let p = new Promise((resolve, reject) => {
    // resolve("ok");       // pending => fulfilled
    // reject("error");    // pending => rejected
    // throw "err";        // pending => rejected
});

能否执行多个回调

一个 promise 指定多个成功/失败回调时,当它的状态改变,都会得到调用

javascript 复制代码
let p = new Promise((resolve, reject) => {
    resolve("OK");
});

// 下面2个回调在 p 的状态改变时都会执行
p.then(value1 => console.log("value1"));
p.then(value2 => console.log("value2"));

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

都有可能:正常情况下是先指定回调 再改变状态(比如 fetch api),但也可以先改变状态 再指定回调(在执行器中直接同步调用 resolve/reject,然后再调用 then)

何时才能得到数据(回调何时执行):

如果先指定回调,那么当状态发生改变时,回调函数就会调用,得到数据

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

then 方法返回的新的 promise 的结果的状态由什么决定

由 then 指定的回调函数的执行结果决定:

  • 如果抛出异常,新 promise 变为 rejected
  • 如果返回非 promise 的任何值,新的 promise 变为 fulfilled,value 为返回的值
  • 如果返回的是一个新的 promise,此 promise 的结果就会成为新的 promise 结果
javascript 复制代码
let p4 = p.then(
    value => {
        console.log(value);
        // throw "err";    // 新的 promise 变为 rejected
        // return 22;      // 新的 promise 变为 fulfilled
        // return new Promise((resolve, reject) => {
        //     reject("error");
        // });             // 此promise 就是新的 promise 结果

    },
    reason => console.warn(reason)
);

Promise 如何串连多个操作任务

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

javascript 复制代码
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("OK");
    }, 1000);
});

p.then(value => {
    return new Promise((resolve, reject) => {
        resolve("success");
    });
}).then(value => console.log(value));

如果继续在后面调用 then 并打印 value,将会输出 undefined,因为上面的最后一个 then 没有返回值,那么就返回了 undefined

异常穿透

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

javascript 复制代码
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        // resolve("OK");
        reject("err");
    }, 1000);
});

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

中断 promise 调用链

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

方法:在回调函数中返回一个 pendding 状态的 promise 对象

手撕 Promise

promise 的参数是 function executor(resolve, reject) {},其中两个形参 resolvereject 作为两个钩子函数在 promise 中实现

javascript 复制代码
function MyPromise(executor) {
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    const self = this;  // 预先保存 this,因为下面函数的 this 指向 window
    function resolve(data) {
        if (self.PromiseState !== 'pending') return;    // 状态只能改变一次
        // 1. 修改对象状态 PromiseState
        self.PromiseState = 'fulfilled';
        // 2. 设置对象结果值 PromiseResult
        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);
    }
}

MyPromise.prototype.then = function (onResolved, onRejected)  {}

let p = new MyPromise((resolve, reject) => {
    // resolve("OK");
    throw "fuck";
});
console.log(p);

then 方法执行回调

javascript 复制代码
MyPromise.prototype.then = function (onResolved, onRejected) {
    if (this.PromiseState === 'fulfilled') {
        onResolved(this.PromiseResult);
    }
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
}

异步任务回调的执行

上面 then 函数实现的问题在于,它是同步执行的。比如一个 pending 状态的 promise 调用 then 后不会产生任何效果,因为没有对应的处理逻辑

回想一下,异步任务被包装在了 executor() 中,分别由 resolve/reject 在不同的情况下执行(改变状态,设置结果)

所以需要在 then 中指定保存回调函数,然后在 resolve/reject 中判空并执行异步任务

javascript 复制代码
function MyPromise(executor) {
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    this.callback = {};
    const self = this;  // 预先保存 this,因为下面函数的 this 指向 window
    function resolve(data) {
        if (self.PromiseState !== 'pending') return;    // 状态只能改变一次
        // 1. 修改对象状态 PromiseState
        self.PromiseState = 'fulfilled';
        // 2. 设置对象结果值 PromiseResult
        self.PromiseResult = data;
        self.callback.onResolved && self.callback.onResolved(data); // 执行回调
    }
    function reject(data) {
        if (self.PromiseState !== 'pending') return;
        self.PromiseState = 'rejected';
        self.PromiseResult = data;
        self.callback.onRejected && self.callback.onRejected(data);
    }
    try {
        executor(resolve, reject);
    } catch(e) {
        reject(e);
    }
}

MyPromise.prototype.then = function (onResolved, onRejected)  {
    if (this.PromiseState === 'fulfilled') {
        onResolved(this.PromiseResult);
    }
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
    if (this.PromiseState === 'pending') {  // 保存回调
        this.callback = {
            onResolved, onRejected
        }
    }
}
let p = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        // resolve("OK")
        reject("err");
    }, 1000);
    // throw "fuck";
});
console.log(p);

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

指定多个回调

只需要将 callback 对象变为数组,每次调用 then 都向 callbacks 中压入 2 个回调,同时在 resolve 和 reject 中执行所有回调

javascript 复制代码
function MyPromise(executor) {
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    this.callbacks = [];
    const self = this;  // 预先保存 this,因为下面函数的 this 指向 window
    function resolve(data) {
        if (self.PromiseState !== 'pending') return;    // 状态只能改变一次
        // 1. 修改对象状态 PromiseState
        self.PromiseState = 'fulfilled';
        // 2. 设置对象结果值 PromiseResult
        self.PromiseResult = data;
        self.callbacks.forEach(cb => cb.onResolved(data));  // 执行回调
    }
    function reject(data) {
        if (self.PromiseState !== 'pending') return;
        self.PromiseState = 'rejected';
        self.PromiseResult = data;
        self.callbacks.forEach(cb => cb.onRejected(data));
    }
    try {
        executor(resolve, reject);
    } catch(e) {
        reject(e);
    }
}

MyPromise.prototype.then = function (onResolved, onRejected)  {
    if (this.PromiseState === 'fulfilled') {
        onResolved(this.PromiseResult);
    }
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
    if (this.PromiseState === 'pending') {  // 保存回调
        this.callbacks.push({
            onResolved, onRejected
        });
    }
}
let p = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve("OK")
        // reject("err");
    }, 1000);
    // throw "fuck";
});
console.log(p);

p.then(value => console.log(value), reason => console.warn(reason));
p.then(value => console.warn(value), reason => console.error(reason));

同步修改状态 then 方法返回 Promise

上面的 then 方法返回的是 undefined,而实际上 then 方法应该永远返回一个 promise

如果 onResolved 返回一个非 promise 对象,则将其包裹为一个 fulfilled 的 promise 返回

如果 onResolved 返回的是一个 promise 对象,则 then 返回的 promise 状态与之保持一致

javascript 复制代码
MyPromise.prototype.then = function (onResolved, onRejected)  {
    return new MyPromise((resolve, reject) => {
        if (this.PromiseState === 'fulfilled') {
            try {
            	// then 方法"成功回调"的执行结果
                let result = onResolved(this.PromiseResult);
                if (result instanceof MyPromise) {
                    result.then(v => resolve(v), r => reject(r));
                } else {
                    resolve(result);
                }
            } catch(e) {
                reject(e);
            }
        }
        if (this.PromiseState === 'rejected') {
            onRejected(this.PromiseResult);
        }
        if (this.PromiseState === 'pending') {  // 保存回调
            this.callbacks.push({
                onResolved, onRejected
            });
        }
    });
}
let p = new MyPromise((resolve, reject) => {
    resolve("OK");
});
console.log(p);

let res = p.then(value => console.log(value), reason => console.warn(reason));
console.log(res);   // -> promise

异步修改状态 then 方法返回 Promise

上面的 then 方法代码只能对已经处于 fulfilled 状态的 promise 进行处理(获取 onResolved 的返回值)

而对于一个处于 pending 状态的 promise 调用 then 后,它虽然保存了回调,但是它保存的只是用户指定的原始的 onResolved/onRejected,并没有包裹在 promise 中,所以我们需要对其进行类似的异步化处理

javascript 复制代码
MyPromise.prototype.then = function (onResolved, onRejected)  {
    const self = this;
    return new MyPromise((resolve, reject) => {
        if (this.PromiseState === 'fulfilled') {
            try {
                let result = onResolved(this.PromiseResult);
                if (result instanceof MyPromise) {
                    result.then(v => resolve(v), r => reject(r));
                } else {
                        resolve(result);
                }
            } catch(e) {
                reject(e);
            }
        }
        if (this.PromiseState === 'rejected') {
            onRejected(this.PromiseResult);
        }
        if (this.PromiseState === 'pending') {  // 保存回调
            this.callbacks.push({
                onResolved: function() {
                    try {
                        let result = onResolved(self.PromiseResult);
                        if (result instanceof MyPromise) {
                            result.then(v => resolve(v), r => reject(r));
                        } else {
                            resolve(result);	// 改变当前 promise 状态
                        }
                    } catch(e) {
                        reject(e);
                    }
                },
                onRejected: function() {
                    try {
                        let result = onRejected(self.PromiseResult);
                        if (result instanceof MyPromise) {
                            result.then(v => resolve(v), r => reject(r));
                        } else {
                            resolve(result);
                        }
                    } catch (e) {
                        reject(e);
                    }
                }
            });
        }
    });
}
let p = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve("OK");
    }, 1000);
});
console.log(p);

let res = p.then(value => console.log(value), reason => console.warn(reason));
console.log(res);   // -> promise

then 方法完善与优化

抽取重复函数进行封装:将 onResolved 和 onRejected 函数参数化为 type 即可

javascript 复制代码
MyPromise.prototype.then = function (onResolved, onRejected)  {
    const self = this;
    return new MyPromise((resolve, reject) => {
        function callback(type) {
            try {
                let result = type(self.PromiseResult);
                if (result instanceof MyPromise) {
                    result.then(v => resolve(v), r => reject(r));
                } else {
                    resolve(result);
                }
            } catch(e) {
                reject(e);
            }
        }

        if (this.PromiseState === 'fulfilled') {
            callback(onResolved);
        }
        if (this.PromiseState === 'rejected') {
            callback(onRejected);
        }
        if (this.PromiseState === 'pending') {  // 保存回调
            this.callbacks.push({
                onResolved: function() {callback(onResolved)},
                onRejected: function() {callback(onRejected)}
            });
        }
    });
}

实现 catch 方法

javascript 复制代码
MyPromise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected);
}

catch 方法可以直接复用 then 进行实现:只传 onRejectd,不传 onResolved

为了实现异常穿透,需要保证 then 方法可以只接受一个参数,所以需要给 onReject 赋予默认值

then 方法甚至允许不接受参数,直接向前推进,所以需要考虑给 onResolved 也赋予默认值

javascript 复制代码
MyPromise.prototype.then = function (onResolved, onRejected)  {
    const self = this;
    if (typeof onRejected !== 'function') {
        onRejected = reason => {
            throw reason	// 如果不定义 onReject,直接抛异常,层层传递,实现链式调用
        }
    }
    if (typeof onResolved !== 'function') {
        onResolved = value => value;
    }
    return new MyPromise((resolve, reject) => {
        function callback(type) {
            try {
                let result = type(self.PromiseResult);
                if (result instanceof MyPromise) {
                    result.then(v => resolve(v), r => reject(r));
                } else {
                    resolve(result);
                }
            } catch(e) {
                reject(e);
            }
        }

        if (this.PromiseState === 'fulfilled') {
            callback(onResolved);
        }
        if (this.PromiseState === 'rejected') {
            callback(onRejected);
        }
        if (this.PromiseState === 'pending') {  // 保存回调
            this.callbacks.push({
                onResolved: function() {callback(onResolved)},
                onRejected: function() {callback(onRejected)}
            });
        }
    });
}
let p = new MyPromise((resolve, reject) => {
    // setTimeout(() => {
    //     resolve("OK");
    // }, 1000);
    reject("err");
});

MyPromise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected);
}

let res = p.then(v => console.log(111))
            .then(v => console.log(222))
            .catch(reason => console.warn(reason));
console.log(res);   // -> promise

实现 resolve 方法

javascript 复制代码
MyPromise.resolve = function (value) {
    return new MyPromise((resolve, reject) => {
        if (value instanceof MyPromise) {
            value.then(v => resolve(v), r => reject(r));
        } else {
            resolve(value);
        }
    });
}

// let p = MyPromise.resolve("hhh");
let p = MyPromise.resolve(new MyPromise((resolve, reject) => {
    reject("error");
}));
console.log(p);
p.then(value => console.log(value)).catch(reason => console.warn(reason));

实现 reject 方法

javascript 复制代码
MyPromise.reject = function (reason) {
    return new MyPromise((resolve, reject) => {
        reject(reason);
   });
}

let p = MyPromise.reject("hhh");
// let p = MyPromise.reject(new MyPromise((resolve, reject) => {
//     resolve("error");
// }));
console.log(p);
p.then(value => console.log(value)).catch(reason => console.warn(reason));

实现 all 方法

javascript 复制代码
MyPromise.all = function (promises) {
    return new MyPromise((resolve, reject) => {
        let count = 0;
        let values = [];
        for (let i = 0; i < promises.length; i++) {
            promises[i].then( v => {
                count++;
                values[i] = v;
                if (count === promises.length) {
                    resolve(values);
                }
            }, r => {
                reject(r);
            });
        }
    });
}

let p1 = MyPromise.resolve("hhh");
let p2 = MyPromise.resolve(new MyPromise((resolve, reject) => {
    resolve("HHH");
}));
MyPromise.all([p1, p2]).then(value => console.log(value), reason => console.warn(reason));

实现 race 方法

javascript 复制代码
MyPromise.race = function (promises) {
    return new MyPromise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(v => resolve(v), r => reject(r));
        }
    });
}

let p2 = MyPromise.resolve(new MyPromise((resolve, reject) => {
    resolve("HHH");
}));
let p1 = MyPromise.resolve("hhh");
MyPromise.race([p1, p2]).then(value => console.log(value), reason => console.warn(reason));

then 方法回调异步执行

通过 settimeout 使得操作异步执行

javascript 复制代码
function MyPromise(executor) {
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    this.callbacks = [];
    const self = this;  // 预先保存 this,因为下面函数的 this 指向 window
    function resolve(data) {
        if (self.PromiseState !== 'pending') return;    // 状态只能改变一次
        // 1. 修改对象状态 PromiseState
        self.PromiseState = 'fulfilled';
        // 2. 设置对象结果值 PromiseResult
        self.PromiseResult = data;
        setTimeout(() => {
            self.callbacks.forEach(cb => cb.onResolved(data));  // 执行回调
        });
    }
    function reject(data) {
        if (self.PromiseState !== 'pending') return;
        self.PromiseState = 'rejected';
        self.PromiseResult = data;
        setTimeout(() => {
            self.callbacks.forEach(cb => cb.onRejected(data));
        });
    }
    try {
        executor(resolve, reject);
    } catch(e) {
        reject(e);
    }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
    const self = this;
    if (typeof onRejected !== 'function') {
        onRejected = reason => {
            throw reason
        }
    }
    if (typeof onResolved !== 'function') {
        onResolved = value => value;
    }
    return new MyPromise((resolve, reject) => {
        function callback(type) {
            try {
                let result = type(self.PromiseResult);
                if (result instanceof MyPromise) {
                    result.then(v => resolve(v), r => reject(r));
                } else {
                    resolve(result);
                }
            } catch(e) {
                reject(e);
            }
        }

        if (this.PromiseState === 'fulfilled') {
            setTimeout(() => {
                callback(onResolved);
            });
        }
        if (this.PromiseState === 'rejected') {
            setTimeout(() => {
                callback(onRejected);
            });
        }
        if (this.PromiseState === 'pending') {  // 保存回调
            this.callbacks.push({
                onResolved: function() {callback(onResolved)},
                onRejected: function() {callback(onRejected)}
            });
        }
    });
}

class 版本实现

javascript 复制代码
class MyPromise {
    constructor (executor) {
        this.PromiseState = 'pending';
        this.PromiseResult = null;
        this.callbacks = [];
        const self = this;  // 预先保存 this,因为下面函数的 this 指向 window
        function resolve(data) {
            if (self.PromiseState !== 'pending') return;    // 状态只能改变一次
            // 1. 修改对象状态 PromiseState
            self.PromiseState = 'fulfilled';
            // 2. 设置对象结果值 PromiseResult
            self.PromiseResult = data;
            setTimeout(() => {
                self.callbacks.forEach(cb => cb.onResolved(data));  // 执行回调
            });
        }
        function reject(data) {
            if (self.PromiseState !== 'pending') return;
            self.PromiseState = 'rejected';
            self.PromiseResult = data;
            setTimeout(() => {
                self.callbacks.forEach(cb => cb.onRejected(data));
            });
        }
        try {
            executor(resolve, reject);
        } catch(e) {
            reject(e);
        }
    }

    then(onResolved, onRejected) {
        const self = this;
        if (typeof onRejected !== 'function') {
            onRejected = reason => {
                throw reason
            }
        }
        if (typeof onResolved !== 'function') {
            onResolved = value => value;
        }
        return new MyPromise((resolve, reject) => {
            function callback(type) {
                try {
                    let result = type(self.PromiseResult);
                    if (result instanceof MyPromise) {
                        result.then(v => resolve(v), r => reject(r));
                    } else {
                        resolve(result);
                    }
                } catch(e) {
                    reject(e);
                }
            }

            if (this.PromiseState === 'fulfilled') {
                setTimeout(() => {
                    callback(onResolved);
                });
            }
            if (this.PromiseState === 'rejected') {
                setTimeout(() => {
                    callback(onRejected);
                });
            }
            if (this.PromiseState === 'pending') {  // 保存回调
                this.callbacks.push({
                    onResolved: function() {callback(onResolved)},
                    onRejected: function() {callback(onRejected)}
                });
            }
        });
    }

    catch(onRejected) {
        return this.then(undefined, onRejected);
    }

    static resolve(value) {
        return new MyPromise((resolve, reject) => {
            if (value instanceof MyPromise) {
                value.then(v => resolve(v), r => reject(r));
            } else {
                resolve(value);
            }
        });
    }

    static reject(reason) {
        return new MyPromise((resolve, reject) => {
            reject(reason);
        });
    }

    static all(promises) {
        return new MyPromise((resolve, reject) => {
            let count = 0;
            let values = [];
            for (let i = 0; i < promises.length; i++) {
                promises[i].then( v => {
                    count++;
                    values[i] = v;
                    if (count === promises.length) {
                        resolve(values);
                    }
                }, r => {
                    reject(r);
                });
            }
        });
    }

    static race(promises) {
        return new MyPromise((resolve, reject) => {
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(v => {
                    resolve(v);
                }, r => {
                    reject(r);
                });
            }
        });
    }
}

async 与 await

async 函数

如果返回的是一个非 promise 的结果,则相当于返回 Promise.resolve()

如果返回的是一个 promise 对象,则相当于返回这个 promise 对象本身

如果抛出异常,则返回一个 rejected 的 promise,PromiseResult 设置为异常原因

await 表达式

await 右侧的表达式一般为 promise 对象,但是也可以是其他的值

如果表达式是 promise 对象,await 返回的是 promise 成功的值。如果 promise 发生了异常,则需要使用 try-catch 进行处理

如果表达式是其他值,则直接将此值本身作为 await 的返回值

宏队列和微队列

js 的执行引擎基于事件循环,只有将所有的同步代码都执行完成,才会执行队列中的回调任务

分析下面代码的执行输出(顺序)

javascript 复制代码
setTimeout(() => {
    console.log("marco queue");
}, 0);

Promise.resolve(1).then(value => console.log("micro queue"));

虽然微队列的任务是后放入的,但是他具有较高的优先级,故优先执行 promise 中的回调,然后执行定时器中的回调

sh 复制代码
micro queue
marco queue

只要微队列中还有任务,就必须先执行:每次取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行

javascript 复制代码
setTimeout(() => {
    console.log(2);
    Promise.resolve(3).then(value => console.log(value));	// 在执行宏队列中的任务时,向微队列插入任务
}, 0);

setTimeout(() => {
    console.log(4)
}, 0);

Promise.resolve(1).then(value => console.log(value));
Promise.resolve(5).then(value => console.log(value));
javascript 复制代码
1
5
2
3
4

面试题

分析下面代码的执行循序:

javascript 复制代码
setTimeout(() => {	// 压入宏队列
    console.log(1)
}, 0);

Promise.resolve().then(() => {	// 压入微队列
    console.log(2);
});

Promise.resolve().then(() => {	// 压入微队列
    console.log(4);
});

console.log(3);	// 同步执行

执行结果:

javascript 复制代码
3
2
4
1

分析下面代码的执行顺序:

javascript 复制代码
setTimeout(() => {      // 压入宏队列 
    console.log(1);
}, 0);

new Promise((resolve) => {
    console.log(2);     // 同步执行
    resolve();
}).then(() => {         // 压入微队列
    console.log(3);
}).then(() => {         // 压入微队列
    console.log(4);
});

console.log(5);         // 同步执行

执行结果:

javascript 复制代码
2
5
3
4
1

分析下面代码的执行顺序:

javascript 复制代码
const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
        }, 0);
        resolve(1);
    });
    resolve(2);
    p.then(value => console.log(value));
}));

first().then(value => console.log(value));

console.log(4);

首先同步代码输出 374,因为 p 调用 then 的时候已经是 fulfilled 状态,所以直接将其压入微队列。同理,first() 也返回一个 fulfilled 状态的 promise,所以直接将这个异步任务压入微队列

然后取出微队列中的2个异步任务执行,输出 12。最后输出宏队列中的异步任务 5

javascript 复制代码
3
7
4
1
2
5

分析下面代码的执行顺序:

javascript 复制代码
setTimeout(() => {
    console.log(0);
}, 0);

new Promise((resolve, reject) => {
    console.log(1);
    resolve();
}).then(() => {
    console.log(2);
    new Promise((resolve, reject) => {
        console.log(3);
        resolve();
    }).then(() => {
        console.log(4);
    }).then(() => {
        console.log(5);
    });
}).then(() => {
    console.log(6);
});

new Promise((resolve, reject) => {
    console.log(7);
    resolve();
}).then(() => {
    console.log(8);
});

首先将定时器任务压入宏队列,然后同步执行第一个 promise 的 executor 函数,输出 1,因为他立即成功,所以将 then 中的"输出2的任务"压入微队列

接着同步执行第二个 promise 的 executor 函数,输出 7,因为他立即成功,所以将"输出8的任务"压入微队列

然后从微队列取出"输出2的任务",执行并输出 2,接着又 new 了一个 promise,同步执行它的 executor,输出 3,紧接着这个 promise 成功,所以将"输出4的任务"压入微队列,此时这个 promise 的 executor 已经执行完毕,所以将"输出6的任务"压入微队列

然后从微队列取出"输出8的任务",执行并输出 8

然后从微队列取出"输出4的任务",执行并输出 4,并且把"输出5的任务"压入微队列

然后从微队列中取出"输出6的任务",执行并输出 6

然后从微队列中取出"输出5的任务",执行并输出 5

最后取出宏队列中的任务,输出 0

javascript 复制代码
1
7
2
3
8
4
6
5
0
相关推荐
Cliven_9 分钟前
TypeScript Jest 单元测试 搭建
javascript·typescript·单元测试
随心Coding3 小时前
【零基础入门Go语言】struct 和 interface:Go语言是如何实现继承的?
前端·golang
2401_884810743 小时前
MySQL视图笔记
数据库·笔记·mysql
扶离_flee4 小时前
麦田物语学习笔记:背包物品选择高亮显示和动画
笔记·学习
幸运小圣4 小时前
LeetCode热题100-合并两个有序链表【JavaScript讲解】
javascript·leetcode·链表
我想学LINUX4 小时前
【2024年华为OD机试】 (C卷,100分)- 消消乐游戏(Java & JS & Python&C/C++)
java·c语言·javascript·c++·游戏·华为od
金州饿霸4 小时前
YARN 架构组件及原理
linux·运维·前端
还这么多错误?!5 小时前
webpack打包要义
前端·webpack
小九九的爸爸5 小时前
浅谈ViewBox那些事(一)
前端·svg
ฅQSω[*邱╭5 小时前
写个自己的vue-cli
前端·javascript·vue.js·学习