javascript 中的 Promise 是如何工作的?我的 Promise 实现不一样 [英] How does Promise in javascript work under the hood? My Promise implementation doesn't work the same
问题描述
我是 JS 的新手,我正在尝试了解 Promise 应该如何在幕后工作.这是一个自定义实现,看起来相当不错我:
class MyPromise {构造函数(执行者){this._resolutionQueue = [];this._rejectionQueue = [];this._state = '待定';this._value;this._rejectionReason;尝试 {executor(this._resolve.bind(this), this._reject.bind(this));}赶上(e){this._reject(e);}}_runRejectionHandlers() {while(this._rejectionQueue.length > 0) {var 拒绝 = this._rejectionQueue.shift();尝试 {var returnValue = reject.handler(this._rejectionReason);}赶上(e){拒绝.承诺._拒绝(e);}if (returnValue && returnValue instanceof MyPromise) {returnValue.then(function (v) {拒绝.承诺._解决(v);}).catch(函数(e){拒绝.承诺._拒绝(e);});} 别的 {拒绝.promise._resolve(returnValue);}}}_runResolutionHandlers() {while(this._resolutionQueue.length > 0) {var resolution = this._resolutionQueue.shift();尝试 {var returnValue = resolution.handler(this._value);}赶上(e){resolution.promise._reject(e);}if (returnValue && returnValue instanceof MyPromise) {returnValue.then(function (v) {resolution.promise._resolve(v);}).catch(函数(e){resolution.promise._reject(e);});} 别的 {resolution.promise._resolve(returnValue);}}}_拒绝(原因){if (this._state === 'pending') {this._rejectionReason = 原因;this._state = '拒绝';this._runRejectionHandlers();while(this._resolutionQueue.length > 0) {var resolution = this._resolutionQueue.shift();resolution.promise._reject(this._rejectionReason);}}}_解析(值){if (this._state === 'pending') {this._value = 值;this._state = '已解决';this._runResolutionHandlers();}}然后(resolutionHandler,rejectionHandler){var newPromise = new MyPromise(function () {});this._resolutionQueue.push({处理程序:分辨率处理程序,承诺:新承诺});如果(类型拒绝处理程序 === '函数'){this._rejectionQueue.push({处理程序:拒绝处理程序,承诺:新承诺});}如果(this._state === '已解决'){this._runResolutionHandlers();}if (this._state === 'rejected') {newPromise._reject(this._rejectionReason);}返回新承诺;}抓住(拒绝处理程序){var newPromise = new MyPromise(function () {});this._rejectionQueue.push({处理程序:拒绝处理程序,承诺:新承诺});if (this._state === 'rejected') {this._runRejectionHandlers();}返回新承诺;}}module.exports = MyPromise;
如您所见,这个实现与多线程本身无关,它只是用纯 JavaScript 编码,没有使用任何 WebAPI.另外,人们说在 中实现了内置 Promise 没有多线程StackOverflow.
这个 MyPromise 在大多数情况下都可以正常工作.但是,在某些情况下,MyPromise 与内置 Promise 的工作方式不同,为什么?"是我的问题.
这是有问题的代码片段:
new MyPromise((resolve, reject) => {console.log("第一个承诺");解决(1);}).then((res) => {console.log("它在里面");返回 res+1;});console.log("终于到了");
执行代码吐出第一个promise"->就在那时"->就在最后",然而,
new Promise((resolve, reject) => {console.log("第一个承诺");解决(1);}).then((res) => {console.log("它在里面");返回 res+1;});console.log("终于到了");
另一方面,这会吐出第一个承诺"->它在最后"->它在那时"
内置 Promise 的行为看起来不正确,除非then"方法实现与MyPromise.then"根本不同.即使考虑到任务队列"和事件循环",我也没有很好地解释为什么存在差异.
我认为 'new Promise(f1).then(f2).then(f3);f4()' 必须按照 f1、f2、f3 和 f4 的顺序依次执行,除非它们包含 WebAPI像 setTimeout 或 $Ajax 里面.但我的小实验并没有这么说,f1、f4、f2……你懂的.
'then' 方法是基于某个工作线程还是什么?我完全迷路了.
请给我一些启示.谢谢.
每个 .then
或 .catch
在一个已解决或被拒绝的 Promise 上应该只在 microtask,在当前运行的同步代码的其余部分完成后.例如,使用以下代码:
Promise.resolve().then(() => console.log('foo'));console.log('bar');
bar
应该在 foo
之前记录.
对于您的代码,最简单的调整是更改 _runRejectionHandlers
(和 _runResolutionHandlers
),以便它们在延迟后而不是立即运行关联的回调:>
class MyPromise {构造函数(执行者){this._resolutionQueue = [];this._rejectionQueue = [];this._state = '待定';this._value;this._rejectionReason;尝试 {executor(this._resolve.bind(this), this._reject.bind(this));}赶上(e){this._reject(e);}}_runRejectionHandlers() {setTimeout(() => {而 (this._rejectionQueue.length > 0) {var 拒绝 = this._rejectionQueue.shift();尝试 {var returnValue = reject.handler(this._rejectionReason);}赶上(e){拒绝.承诺._拒绝(e);}if (returnValue && returnValue instanceof MyPromise) {returnValue.then(function(v) {拒绝.承诺._解决(v);}).catch(函数(e){拒绝.承诺._拒绝(e);});} 别的 {拒绝.promise._resolve(returnValue);}}});}_runResolutionHandlers() {setTimeout(() => {而 (this._resolutionQueue.length > 0) {var resolution = this._resolutionQueue.shift();尝试 {var returnValue = resolution.handler(this._value);}赶上(e){resolution.promise._reject(e);}if (returnValue && returnValue instanceof MyPromise) {returnValue.then(function(v) {resolution.promise._resolve(v);}).catch(函数(e){resolution.promise._reject(e);});} 别的 {resolution.promise._resolve(returnValue);}}});}_拒绝(原因){if (this._state === 'pending') {this._rejectionReason = 原因;this._state = '拒绝';this._runRejectionHandlers();而 (this._resolutionQueue.length > 0) {var resolution = this._resolutionQueue.shift();resolution.promise._reject(this._rejectionReason);}}}_解析(值){if (this._state === 'pending') {this._value = 值;this._state = '已解决';this._runResolutionHandlers();}}然后(resolutionHandler,rejectionHandler){var newPromise = new MyPromise(function() {});this._resolutionQueue.push({处理程序:分辨率处理程序,承诺:新承诺});如果(类型拒绝处理程序 === '函数'){this._rejectionQueue.push({处理程序:拒绝处理程序,承诺:新承诺});}如果(this._state === '已解决'){this._runResolutionHandlers();}if (this._state === 'rejected') {newPromise._reject(this._rejectionReason);}返回新承诺;}抓住(拒绝处理程序){var newPromise = new MyPromise(function() {});this._rejectionQueue.push({处理程序:拒绝处理程序,承诺:新承诺});if (this._state === 'rejected') {this._runRejectionHandlers();}返回新承诺;}}new MyPromise((resolve, reject) => {console.log("第一个承诺");解决(1);}).then((res) => {console.log("它在里面");返回 res + 1;});console.log("终于到了");
理想情况下,延迟将通过微任务完成,例如:
Promise.resolve().then(() => {//其余代码});
但是由于 Promise
是您已经尝试实现的那种功能,您可能不想这样做,因此您可以改用宏任务:
setTimeout(() => {//其余代码});
这不会完全符合规范,但我不确定还有其他选择.
I am a newbie to JS and I am trying to understand how Promise should work under the hood. Here is a custom implementation that looks reasonably good to me:
class MyPromise {
constructor(executor) {
this._resolutionQueue = [];
this._rejectionQueue = [];
this._state = 'pending';
this._value;
this._rejectionReason;
try {
executor(this._resolve.bind(this), this._reject.bind(this));
} catch (e) {
this._reject(e);
}
}
_runRejectionHandlers() {
while(this._rejectionQueue.length > 0) {
var rejection = this._rejectionQueue.shift();
try {
var returnValue = rejection.handler(this._rejectionReason);
} catch(e) {
rejection.promise._reject(e);
}
if (returnValue && returnValue instanceof MyPromise) {
returnValue.then(function (v) {
rejection.promise._resolve(v);
}).catch(function (e) {
rejection.promise._reject(e);
});
} else {
rejection.promise._resolve(returnValue);
}
}
}
_runResolutionHandlers() {
while(this._resolutionQueue.length > 0) {
var resolution = this._resolutionQueue.shift();
try {
var returnValue = resolution.handler(this._value);
} catch(e) {
resolution.promise._reject(e);
}
if (returnValue && returnValue instanceof MyPromise) {
returnValue.then(function (v) {
resolution.promise._resolve(v);
}).catch(function (e) {
resolution.promise._reject(e);
});
} else {
resolution.promise._resolve(returnValue);
}
}
}
_reject(reason) {
if (this._state === 'pending') {
this._rejectionReason = reason;
this._state = 'rejected';
this._runRejectionHandlers();
while(this._resolutionQueue.length > 0) {
var resolution = this._resolutionQueue.shift();
resolution.promise._reject(this._rejectionReason);
}
}
}
_resolve(value) {
if (this._state === 'pending') {
this._value = value;
this._state = 'resolved';
this._runResolutionHandlers();
}
}
then(resolutionHandler, rejectionHandler) {
var newPromise = new MyPromise(function () {});
this._resolutionQueue.push({
handler: resolutionHandler,
promise: newPromise
});
if (typeof rejectionHandler === 'function') {
this._rejectionQueue.push({
handler: rejectionHandler,
promise: newPromise
});
}
if (this._state === 'resolved') {
this._runResolutionHandlers();
}
if (this._state === 'rejected') {
newPromise._reject(this._rejectionReason);
}
return newPromise;
}
catch(rejectionHandler) {
var newPromise = new MyPromise(function () {});
this._rejectionQueue.push({
handler: rejectionHandler,
promise: newPromise
});
if (this._state === 'rejected') {
this._runRejectionHandlers();
}
return newPromise;
}
}
module.exports = MyPromise;
As you see, this implementation has nothing to do with multi-threading itself, it's just coded in pure javascript without using any WebAPIs. Also, people say the built-in Promise is implemented without multi-threading in StackOverflow.
This MyPromise just work fine for most cases. However, MyPromise doesn't work the same as the built-in Promise in some cases and 'why?' is my question.
Here is the problematic code snippet:
new MyPromise((resolve, reject) => {
console.log("first promise");
resolve(1);
}).then((res) => {
console.log("it's in then");
return res+1;
}); console.log("it's in the end");
Executing the code spits out "first promise" -> "it's in then" -> "It's in the end", However,
new Promise((resolve, reject) => {
console.log("first promise");
resolve(1);
}).then((res) => {
console.log("it's in then");
return res+1;
}); console.log("it's in the end");
On the other hand, this spits out "first promise" -> "it's in the end" -> "it's in then"
The behavior of the builtin Promise doesn't look right unless the 'then' method implementation is fundamentally different from 'MyPromise.then'. Even taking 'task queue' and 'event loop' into account, I don't see a good explanation on why the difference.
I thought 'new Promise(f1).then(f2).then(f3);f4()' must be executed in the order of f1, f2, f3 and then f4, in a series, unless they include WebAPIs like setTimeout or $Ajax inside. But my little experiment doesn't say so, f1, f4, f2, ... you got the idea.
Is the 'then' method based upon some worker thread or something? I am totally lost.
Please shed some light on me. Thanks.
Each .then
or .catch
on a resolved or rejected Promise should run only during a microtask, after the rest of the current running synchronous code has completed. For example, with the following code:
Promise.resolve()
.then(() => console.log('foo'));
console.log('bar');
bar
should be logged before foo
.
For your code, the simplest tweak would be to change _runRejectionHandlers
(and _runResolutionHandlers
) so that they run their associated callbacks after a delay, rather than immediately:
class MyPromise {
constructor(executor) {
this._resolutionQueue = [];
this._rejectionQueue = [];
this._state = 'pending';
this._value;
this._rejectionReason;
try {
executor(this._resolve.bind(this), this._reject.bind(this));
} catch (e) {
this._reject(e);
}
}
_runRejectionHandlers() {
setTimeout(() => {
while (this._rejectionQueue.length > 0) {
var rejection = this._rejectionQueue.shift();
try {
var returnValue = rejection.handler(this._rejectionReason);
} catch (e) {
rejection.promise._reject(e);
}
if (returnValue && returnValue instanceof MyPromise) {
returnValue.then(function(v) {
rejection.promise._resolve(v);
}).catch(function(e) {
rejection.promise._reject(e);
});
} else {
rejection.promise._resolve(returnValue);
}
}
});
}
_runResolutionHandlers() {
setTimeout(() => {
while (this._resolutionQueue.length > 0) {
var resolution = this._resolutionQueue.shift();
try {
var returnValue = resolution.handler(this._value);
} catch (e) {
resolution.promise._reject(e);
}
if (returnValue && returnValue instanceof MyPromise) {
returnValue.then(function(v) {
resolution.promise._resolve(v);
}).catch(function(e) {
resolution.promise._reject(e);
});
} else {
resolution.promise._resolve(returnValue);
}
}
});
}
_reject(reason) {
if (this._state === 'pending') {
this._rejectionReason = reason;
this._state = 'rejected';
this._runRejectionHandlers();
while (this._resolutionQueue.length > 0) {
var resolution = this._resolutionQueue.shift();
resolution.promise._reject(this._rejectionReason);
}
}
}
_resolve(value) {
if (this._state === 'pending') {
this._value = value;
this._state = 'resolved';
this._runResolutionHandlers();
}
}
then(resolutionHandler, rejectionHandler) {
var newPromise = new MyPromise(function() {});
this._resolutionQueue.push({
handler: resolutionHandler,
promise: newPromise
});
if (typeof rejectionHandler === 'function') {
this._rejectionQueue.push({
handler: rejectionHandler,
promise: newPromise
});
}
if (this._state === 'resolved') {
this._runResolutionHandlers();
}
if (this._state === 'rejected') {
newPromise._reject(this._rejectionReason);
}
return newPromise;
}
catch (rejectionHandler) {
var newPromise = new MyPromise(function() {});
this._rejectionQueue.push({
handler: rejectionHandler,
promise: newPromise
});
if (this._state === 'rejected') {
this._runRejectionHandlers();
}
return newPromise;
}
}
new MyPromise((resolve, reject) => {
console.log("first promise");
resolve(1);
}).then((res) => {
console.log("it's in then");
return res + 1;
});
console.log("it's in the end");
Ideally, the delay would be done via a microtask, like:
Promise.resolve()
.then(() => {
// rest of the code
});
But since Promise
is the sort of functionality you're trying to implement already, you may not want to do that, so you can use a macrotask instead:
setTimeout(() => {
// rest of the code
});
That won't be fully spec-compliant, but I'm not sure there are any other options.
这篇关于javascript 中的 Promise 是如何工作的?我的 Promise 实现不一样的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!