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
相关推荐
BillKu15 分钟前
TypeScript中,字符串字面量联合类型(Union Type)、enum的用法说明
前端·javascript·typescript
jayson.h18 分钟前
PDF 合并+添加页码 相关库、类、函数
开发语言·前端·python
GGMM78930 分钟前
西门子BOP20基本操作面板超详细教程(S120/G130专用)
笔记·变频器·变频器维修
一条破秋裤1 小时前
TIM 输入捕获与 PWMI:从边沿到频率、占空比
笔记·stm32·学习
慧一居士1 小时前
Sass和Less功能、使用场景、用法对比
前端·css·less·sass
leoZ2312 小时前
AI+前端提效-09 AI赋能前端测试:单元测试、E2E测试自动生成,提升覆盖率
前端·人工智能·opencv·目标检测·数据挖掘·单元测试·语音识别
知产xiao_xin2 小时前
知识产权入门 —— 工业品外观设计
经验分享·笔记·知识产权
计算机魔术师2 小时前
OpenAI 评定 Astra 达到网络安全 Critical 能力阈值,将受限发布
前端
JavaGuide2 小时前
SpaceX 工程师的 AI Coding 玩法太牛了, 200 多个 Agent 并行!
前端·后端
小猪code2 小时前
frp 80/443 Web 服务两种部署模式实操笔记
前端·笔记