【问题标题】:How does Promise in javascript work under the hood? My Promise implementation doesn't work the samejavascript 中的 Promise 是如何工作的?我的 Promise 实现不一样
【发布时间】:2019-12-23 04:56:33
【问题描述】:

我是 JS 的新手,我正在尝试了解 Promise 应该如何在幕后工作。 这是一个custom implementation,对我来说看起来相当不错:

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;

如您所见,此实现与多线程本身无关,它只是用纯 javascript 编码,没有使用任何 WebAPI。另外,有人说StackOverflow 中内置的 Promise 是在没有多线程的情况下实现的。

这个 MyPromise 在大多数情况下都可以正常工作。但是,在某些情况下,MyPromise 与内置 Promise 的工作方式不同,“为什么?”是我的问题。

这里是有问题的代码sn-p:

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");

执行代码吐出“first promise” -> “it's in then” -> “It's in the end”,然而,

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");

另一方面,这会吐出“第一个承诺”->“它在最后”->“它在那个时候”

除非 'then' 方法实现与 'MyPromise.then' 根本不同,否则内置 Promise 的行为看起来不正确。即使考虑到“任务队列”和“事件循环”,我也没有很好地解释为什么存在差异。

我认为 'new Promise(f1).then(f2).then(f3);f4()' 必须按 f1、f2、f3 和 f4 的顺序依次执行,除非它们包含 WebAPI比如里面的 setTimeout 或 $Ajax。但是我的小实验不是这样说的,f1、f4、f2、……你懂的。

'then' 方法是基于某个工作线程还是什么?我完全迷路了。

请给我一些启示。谢谢。

【问题讨论】:

  • 在 javascript 中有许许多多的 Promise 实现 - 请参阅 this list - 您可以阅读其他人的代码以了解他们的工作 - 其中一些有源代码,其中包含对部分的引用the promise A+ spec 正在处理中 - 其中一些非常有启发性
  • 我在自定义 Promise 中实现的方式是 such。我使用setInterval 来完成这项工作。我不知道它是否更好。并且代码按预期输出。

标签: javascript node.js multithreading promise


【解决方案1】:

已解决或已拒绝的 Promise 上的每个 .then.catch 应仅在当前运行的同步代码的其余部分完成后,在 微任务 期间运行。例如,使用以下代码:

Promise.resolve()
  .then(() => console.log('foo'));
console.log('bar');

bar 应该在foo 之前记录。

对于您的代码,最简单的调整是更改 _runRejectionHandlers(和 _runResolutionHandlers),以便它们在延迟后而不是立即运行关联的回调:

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");

理想情况下,延迟将通过微任务完成,例如:

Promise.resolve()
  .then(() => {
    // rest of the code
  });

但由于Promise 是您已经尝试实现的那种功能,您可能不想这样做,因此您可以改用宏任务:

setTimeout(() => {
  // rest of the code
});

这不会完全符合规范,但我不确定还有其他选择。

【讨论】:

  • 不能 user3126624 使用 queueMicrotask 直接推入承诺(微任务)队列吗?
  • 是的,这听起来比Promise.resolve 还要好,但是任何支持queueMicrotask 的环境也已经支持PromisequeueMicrotask 也是 非常 新的,比 Promises 更新得多,我很犹豫是否在没有 polyfill 的情况下使用它(并且,鉴于这里的目标 基本上是为了填充它,你自己……)
  • 由于它被标记为 Node,似乎执行环境可能会为新功能的可用性提供帮助,这就是我提出它的原因。我完全同意,在 Web 环境中,polyfill 更适用,尽管由于您正确陈述的原因(宏任务与微任务)并非完全可能。
  • 多亏了你,现在我知道了行为、微任务和宏任务的原因。我将研究这些概念,看看是否可以将它们用于我的玩具实现。据推测,这可能需要我自己的事件循环系统,对吧?
  • @user3126624 宏任务时间无论如何都是依赖于实现的。并且该实现在 Javascript 级别上并不真正可见-它在浏览器/环境内部。我不会打扰,只使用提供的挂钩它们的函数(如setTImeoutqueueMicrotask)更容易。
猜你喜欢
  • 1970-01-01
  • 2021-10-24
  • 2015-03-03
  • 1970-01-01
  • 2023-01-10
  • 2018-06-17
  • 2021-11-10
  • 2020-04-16
  • 1970-01-01
相关资源
最近更新 更多