首页 >web前端 >js教程 >在 JavaScript 中创建您自己的 Promise

在 JavaScript 中创建您自己的 Promise

Barbara Streisand
Barbara Streisand原创
2024-12-28 13:29:10982浏览

Create your own Promise in JavaScript

为什么?

了解 JavaScript Promises 如何在后台异步运行回调。

让我们用 JavaScript 创建我们自己的 Promise!我们将遵循 Promise/A 规范,该规范概述了 Promise 如何处理异步操作、解析、拒绝以及确保可预测的链接和错误处理。

为了简单起见,我们将重点关注 Promises/A 规范中 ✅ 标记的关键规则。这不是一个完整的实现,而是一个简化版本。这是我们将构建的:

1. 术语

1.1 'promise' 是一个带有 then 方法的对象或函数,其行为符合此规范。

1.2 thenable'是一个定义了then方法的对象或函数。

1.3 'value' 是任何合法的 JavaScript 值(包括未定义的、thenable 或一个promise)。

1.4 “异常”是使用 throw 语句抛出的值。

1.5 'reason' 是一个值,表示承诺被拒绝的原因。

2. 要求

2.1 承诺状态

承诺必须处于以下三种状态之一:待定、已履行或已拒绝。

2.1.1。待处理时,承诺:✅

⟶ 可能会转换为已完成或已拒绝状态。

2.1.2。兑现后,承诺:✅

⟶不得转换到任何其他状态。

⟶ 必须有一个值,且不得更改。

2.1.3。当被拒绝时,一个承诺:✅

⟶不得转换到任何其他状态。

⟶必须有一个理由,并且这个理由不能改变。

2.2 then方法

promise 必须提供 then 方法来访问其当前或最终的值或原因。

promise 的 then 方法接受两个参数:

promise.then(onFulfilled, onRejected);

2.2.1。 onFulfilled 和 onRejected 都是可选参数:✅

⟶ 如果 onFulfilled 不是函数,则必须忽略它。

⟶ 如果 onRejected 不是函数,则必须忽略它。

2.2.2。如果 onFulfilled 是一个函数: ✅

⟶ 它必须在 Promise 完成后调用,并以 Promise 的值作为第一个参数。

⟶ 在承诺完成之前不得调用它。

⟶ 不得多次调用。

2.2.3。如果 onRejected 是一个函数,✅

⟶ 它必须在 Promise 被拒绝后调用,并以 Promise 的原因作为第一个参数。

⟶ 在承诺被拒绝之前不得调用它。

⟶ 不得多次调用。

2.2.4。在执行上下文堆栈仅包含平台代码之前,不得调用 onFulfilled 或 onRejected。 ✅

2.2.5。 onFulfilled 和 onRejected 必须作为函数调用(即没有 this 值)。 ✅

2.2.6。 then 可能会针对同一个 Promise 被多次调用。 ✅

⟶ 如果/当 Promise 被履行时,所有相应的 onFulfilled 回调必须按照它们最初调用 then 的顺序执行。

⟶ 如果/当 Promise 被拒绝时,所有相应的 onRejected 回调必须按照其原始调用 then 的顺序执行。

2.2.7。那么必须返回一个承诺。 ✅

promise.then(onFulfilled, onRejected);

⟶ 如果 onFulfilled 或 onRejected 返回值 x,则运行 Promise 解析过程 [[Resolve]](promise2, x)。 ❌

⟶ 如果 onFulfilled 或 onRejected 抛出异常 e,则 Promise2 必须以 e 作为原因被拒绝。 ❌

⟶ 如果 onFulfilled 不是函数并且 Promise1 已实现,则 Promise2 必须以与 Promise1 相同的值来实现。 ❌

⟶ 如果 onRejected 不是函数并且 Promise1 被拒绝,则 Promise2 也必须以与 Promise1 相同的原因被拒绝。 ❌

执行

JavaScript Promise 采用执行器函数作为参数,该函数在 Promise 创建时立即调用:

promise2 = promise1.then(onFulfilled, onRejected);
new Promise(excecutor);

核心 Promises/A 规范不涉及如何创建、履行或拒绝 Promise。由你决定。但是您为 Promise 构造提供的实现必须与 JavaScript 中的异步 API 兼容。这是我们 Promise 类的初稿:

const promise = new Promise((resolve, reject) => {
    // Runs some async or sync tasks
});

规则 2.1(Promise 状态)规定,Promise 必须处于以下三种状态之一:待处理、已履行或已拒绝。它还解释了每个状态中发生的情况。

当履行或拒绝时,承诺不得转变为任何其他状态。因此,我们需要在进行任何转换之前确保 Promise 处于待处理状态:

class YourPromise {
    constructor(executor) {
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;

        const resolve = value => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
            }
        };

        const reject = reason => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
            }
        };

        try {
            executor(resolve, reject);  // The executor function being called immediately
        } catch (error) {
            reject(error);
        }
    }
}

我们已经知道 Promise 的初始状态是待处理的,并且我们确保它保持这种状态,直到明确履行或拒绝:

const resolve = value => {
    if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
    }
};

const reject = reason => {
    if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
    }
};

由于执行器函数在 Promise 实例化后立即被调用,因此我们在构造函数方法中调用它:

this.state = 'pending';

我们的 YourPromise 类的初稿已在此处完成。

Promise/A 规范主要关注定义可互操作的 then() 方法。这个方法让我们可以访问promise的当前或最终值或原因。让我们深入探讨一下。

规则 2.2(Then 方法)规定 Promise 必须有一个 then() 方法,该方法接受两个参数:

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

onFulfilled 和 onRejected 都必须在 Promise 完成或拒绝后调用,如果它们是函数,则传递 Promise 的值或原因作为它们的第一个参数:

class YourPromise {
    constructor(executor) {
        // Implementation
    }

    then(onFulfilled, onRejected) {
        // Implementation
    }
}

此外,在承诺履行或拒绝之前,不得调用它们,也不得调用超过一次。 onFulfilled 和 onRejected 都是可选的,如果它们不是函数,则应忽略它们。

如果你看一下规则 2.2、2.2.6 和 2.2.7,你会发现一个 Promise 必须有一个 then() 方法,then() 方法可以被多次调用,并且它必须返回一个承诺:

promise.then(onFulfilled, onRejected);

为了简单起见,我们不会处理单独的类或函数。我们将返回一个 Promise 对象,并传递一个执行器函数:

promise2 = promise1.then(onFulfilled, onRejected);

在执行器函数中,如果 Promise 被履行,我们会调用 onFulfilled 回调并使用 Promise 的值来解析它。同样,如果 Promise 被拒绝,我们会调用 onRejected 回调并以 Promise 的原因拒绝它。

下一个问题是,如果 Promise 仍处于待处理状态,如何处理 onFulfilled 和 onRejected 回调?我们将它们排队以便稍后调用,如下所示:

new Promise(excecutor);

我们完成了。这是 Promise 类的第二稿,包括 then() 方法:

const promise = new Promise((resolve, reject) => {
    // Runs some async or sync tasks
});

这里,我们引入两个字段:onFulfilledCallbacks 和 onRejectedCallbacks 作为保存回调的队列。当 Promise 未决时,这些队列通过 then() 调用填充回调,并且当 Promise 被履行或拒绝时调用它们。

继续测试你的 Promise 类:

class YourPromise {
    constructor(executor) {
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;

        const resolve = value => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
            }
        };

        const reject = reason => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
            }
        };

        try {
            executor(resolve, reject);  // The executor function being called immediately
        } catch (error) {
            reject(error);
        }
    }
}

它应该输出:

const resolve = value => {
    if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
    }
};

const reject = reason => {
    if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
    }
};

另一方面,如果您运行以下测试:

this.state = 'pending';

你会得到:

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

代替:

class YourPromise {
    constructor(executor) {
        // Implementation
    }

    then(onFulfilled, onRejected) {
        // Implementation
    }
}

为什么?问题在于,当调用 then() 时 YourPromise 实例已被解析或拒绝时,then() 方法如何处理回调。具体来说,当 Promise 状态不是待处理时,then() 方法不会正确地将回调的执行推迟到下一个微任务队列。这会导致同步执行。在我们的示例测试中:

⟶ 承诺立即解决,值为“立即解决”。

⟶ 当调用promise.then()时,状态已经完成,所以onFulfilled回调会直接执行,不会推迟到下一个微任务队列。

这里规则 2.2.4 发挥作用。此规则确保 then() 回调(onFulfilled 或 onRejected)异步执行,即使 Promise 已解决或拒绝。这意味着回调不得运行,直到当前执行堆栈完全清除并且只有平台代码(如事件循环或微任务队列)正在运行。

为什么这条规则很重要?

这条规则是 Promise/A 规范中最重要的规则之一。因为它确保:

⟶ 即使 Promise 立即得到解决,其 then() 回调也不会执行,直到事件循环的下一个标记。

⟶ 此行为与 JavaScript 中其他异步 API(例如 setTimeout 或 process.nextTick)的行为一致。

我们怎样才能做到这一点?

这可以通过宏任务机制(如setTimeout或setImmediate)或微任务机制(如queueMicrotask或process.nextTick)来实现。因为微任务或宏任务或类似机制中的回调将在当前 JavaScript 执行上下文完成后执行。

为了解决上述问题,我们需要确保即使状态已经完成或拒绝,相应的回调(onFulfilled或onRejected)也使用queueMicrotask异步执行。这是更正后的实现:

promise.then(onFulfilled, onRejected);

再次运行前面的示例测试代码。您应该得到以下输出:

promise2 = promise1.then(onFulfilled, onRejected);

就是这样。

现在,您应该清楚地了解 then() 的回调如何延迟并在下一个微任务队列中执行,从而实现异步行为。牢牢掌握这个概念对于在 JavaScript 中编写有效的异步代码至关重要。

下一步是什么?由于本文没有涵盖完整的 Promises/A 规范,您可以尝试实现其余部分以获得更深入的理解。

既然你已经读到这里了,希望你喜欢阅读这篇文章!请分享文章。

关注我:
LinkedIn、Medium 和 Github

以上是在 JavaScript 中创建您自己的 Promise的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn