异步函数外部堆栈上下文

2024-04-21

有时代码想知道特定函数(或子函数)是否正在运行。例如,node.js 有domains https://nodejs.org/api/domain.html它也适用于异步内容(不确定这是否包括异步函数)。

一些简单的代码来解释我需要什么,如下所示:

inUpdate = true;
try {
  doUpdate();
} finally {
  inUpdate = false;
}

然后可以这样使用:

function modifyThings() {
  if (inUpdate) throw new Error("Can't modify while updating");
}

随着async如果 doUpdate() 函数是异步的,则此代码会中断。使用回调式函数当然已经是这样了。

The doUpdate当然可以修补函数以维护每个变量await,但即使您可以控制代码,这也很麻烦且容易出错,并且在尝试跟踪内部异步函数调用时会中断doUpdate.

我尝试了猴子修补 Promise.prototype:

const origThen = Promise.prototype.then;
Promise.prototype.then = function(resolve, reject) {
  const isInUpdate = inUpdate;
  origThen.call(this, function myResolve(value) {
    inUpdate = isInUpdate;
    try {
      return resolve(value);
    } finally {
      inUpdate = false;
    }
  }, reject);
}

不幸的是这不起作用。我不知道为什么,但异步继续代码最终在resolve调用堆栈(可能使用微任务)。

请注意,仅仅执行以下操作还不够:

function runUpdate(doUpdate) {
  inUpdate = true;
  doUpdate.then(() => inUpdate = false).catch(() => inUpdate = false);
}

原因是:

runUpdate(longAsyncFunction);
console.log(inUpdate); // incorrectly returns true

有没有办法从异步函数外部跟踪某些内容,以便可以判断被调用的函数或其任何后代调用是否正在运行?

我知道可以用生成器模拟异步函数yield,在这种情况下我们可以控制调用堆栈(因为我们可以调用gen.next())但这是异步函数的出现刚刚解决的一个问题,所以我专门寻找一个与本机(不是 Babel 生成的)异步函数一起使用的解决方案。

Edit:为了澄清这个问题:假设该代码是异步函数的调用者,是否有一种方法可以让外部代码知道异步函数的特定调用是否正在运行或是否已挂起。它是否正在运行将由最终由异步函数(堆栈中的某个位置)调用的函数确定。

Edit:为了澄清更多:预期的功能将与domains https://nodejs.org/api/domain.html在 Node.js 中,也适用于浏览器。域已经可以与 Promise 一起使用,所以async功能可能也可以工作(未经测试)。


这段代码可以让我在一定程度上做我想做的事情:

function installAsyncTrack() {
  /* global Promise: true */
  if (Promise.isAsyncTracker) throw new Error('Only one tracker can be installed');

  const RootPromise = Promise.isAsyncTracker ? Promise.rootPromise : Promise;
  let active = true;

  const tracker = {
    track(f, o, ...args) {
      const prevObj = tracker.trackObj;
      tracker.trackObj = o;
      try {
        return f.apply(this, args);
      } finally {
        tracker.trackObj = prevObj;
      }
    },
    trackObj: undefined,
    uninstall() {
      active = false;
      if (Promise === AsyncTrackPromise.prevPromise) return;
      if (Promise !== AsyncTrackPromise) return;
      Promise = AsyncTrackPromise.prevPromise;
    }
  };

  AsyncTrackPromise.prototype = Object.create(Promise);
  AsyncTrackPromise.rootPromise = RootPromise;
  AsyncTrackPromise.prevPromise = Promise;
  Promise = AsyncTrackPromise;
  AsyncTrackPromise.resolve = value => {
    return new AsyncTrackPromise(resolve => resolve(value));
  };
  AsyncTrackPromise.reject = val => {
    return new AsyncTrackPromise((resolve, reject) => reject(value));
  };
  AsyncTrackPromise.all = iterable => {
    const promises = Array.from(iterable);
    if (!promises.length) return AsyncTrackPromise.resolve();
    return new AsyncTrackPromise((resolve, reject) => {
      let rejected = false;
      let results = new Array(promises.length);
      let done = 0;
      const allPromises = promises.map(promise => {
        if (promise && typeof promise.then === 'function') {
          return promise;
        }
        return new AsyncTrackPromise.resolve(promise);
      });
      allPromises.forEach((promise, ix) => {
        promise.then(value => {
          if (rejected) return;
          results[ix] = value;
          done++;
          if (done === results.length) {
            resolve(results);
          }
        }, reason => {
          if (rejected) return;
          rejected = true;
          reject(reason);
        });
      });
    });
  };
  AsyncTrackPromise.race = iterable => {
    const promises = Array.from(iterable);
    if (!promises.length) return new AsyncTrackPromise(() => {});
    return new AsyncTrackPromise((resolve, reject) => {
      let resolved = false;
      if (promises.some(promise => {
          if (!promise || typeof promise.then !== 'function') {
            resolve(promise);
            return true;
          }
        })) return;
      promises.forEach((promise, ix) => {
        promise.then(value => {
          if (resolved) return;
          resolved = true;
          resolve(value);
        }, reason => {
          if (resolved) return;
          resolved = true;
          reject(reason);
        });
      });
    });
  };

  function AsyncTrackPromise(handler) {
    const promise = new RootPromise(handler);
    promise.trackObj = tracker.trackObj;

    promise.origThen = promise.then;
    promise.then = thenOverride;

    promise.origCatch = promise.catch;
    promise.catch = catchOverride;

    if (promise.finally) {
      promise.origFinally = promise.finally;
      promise.finally = finallyOverride;
    }
    return promise;
  }

  AsyncTrackPromise.isAsyncTracker = true;

  function thenOverride(resolve, reject) {
    const trackObj = this.trackObj;
    if (!active || trackObj === undefined) return this.origThen.apply(this, arguments);
    return this.origThen.call(
      this,
      myResolver(trackObj, resolve),
      reject && myResolver(trackObj, reject)
    );
  }

  function catchOverride(reject) {
    const trackObj = this.trackObj;
    if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
    return this.origCatch.call(
      this,
      myResolver(trackObj, reject)
    );
  }

  function finallyOverride(callback) {
    const trackObj = this.trackObj;
    if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
    return this.origCatch.call(
      this,
      myResolver(trackObj, reject)
    );
  }

  return tracker;

  function myResolver(trackObj, resolve) {
    return function myResolve(val) {
      if (trackObj === undefined) {
        return resolve(val);
      }
      RootPromise.resolve().then(() => {
        const prevObj = tracker.trackObj;
        tracker.trackObj = trackObj;
        RootPromise.resolve().then(() => {
          tracker.trackObj = prevObj;
        });
      });
      const prevObj = tracker.trackObj;
      tracker.trackObj = trackObj;
      try {
        return resolve(val);
      } finally {
        tracker.trackObj = prevObj;
      }
    };
  }

}

tracker = installAsyncTrack();

function track(func, value, ...args) {
  return tracker.track(func, { value }, value, ...args);
}

function show(where, which) {
  console.log('At call', where, 'from', which, 'the value is: ', tracker.trackObj && tracker.trackObj.value);
}

async function test(which, sub) {
  show(1, which);
  await delay(Math.random() * 100);
  show(2, which);
  if (sub === 'resolve') {
    await Promise.resolve(test('sub'));
    show(3, which);
  }
  if (sub === 'call') {
    await test(which + ' sub');
    show(3, which);
  }
}

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

track(test, 'test1');
track(test, 'test2');
track(test, 'test3', 'resolve');
track(test, 'test4', 'call');

它用我自己的 Promise 替换了原生的 Promise。该 Promise 将当前上下文 (taskObj) 存储在 Promise 上。

当。。。的时候.then调用回调或其同类,它执行以下操作:

  • 它创建了一个立即解决的新原生承诺。这增加了一个新的微任务 https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/到队列(根据规范,所以应该是可靠的)。

  • 它调用原始解决或拒绝。至少在 Chrome 和 Firefox 中,这会在队列中生成另一个微任务,该微任务将运行异步函数的下一部分。还不确定规范对此有何说明。它还会恢复调用周围的上下文,以便如果不是await使用它,这里没有添加微任务。

  • 第一个微任务被执行,这是我的第一个(本机)承诺得到解决。此代码恢复当前上下文 (taskObj)。它还创建一个新的已解决的承诺,将另一个微任务排队

  • 第二个微任务(如果有)被执行,在异步函数中运行 JS,直到遇到下一个微任务await或返回。

  • 第一个微任务排队的微任务被执行,这会将上下文恢复到 Promise 解决/拒绝之前的状态(应该始终是undefined,除非设置在 a 之外tracker.track(...) call).

如果拦截的 Promise 不是本机的(例如 bluebird),它仍然有效,因为它会恢复在resolve(...)(和类似)打电话。

有一种情况我似乎找不到解决方案:

tracker.track(async () => {
  console.log(tracker.taskObj); // 'test'
  await (async () => {})(); //This breaks because the promise generated is native
  console.log(tracker.taskObj); // undefined
}, 'test')

解决方法是将承诺包装在Promise.resolve():

tracker.track(async () => {
  console.log(tracker.taskObj); // 'test'
  await Promise.resolve((async () => {})());
  console.log(tracker.taskObj); // undefined
}, 'test')

显然,需要对所有不同的环境进行大量测试,并且需要针对子调用的解决方法这一事实是痛苦的。此外,所有使用的 Promise 都需要包含在Promise.resolve()或使用全局Promise.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

异步函数外部堆栈上下文 的相关文章

随机推荐

  • Python 子进程调用不能采用 grep [重复]

    这个问题在这里已经有答案了 Python 子进程调用应该按原样作为命令运行 但如果其中有管道 它就会抱怨 这是我的代码 usr bin python import sys import subprocess import time serv
  • 如何在预构建步骤中转义美元符号

    我正在与 Visual Studio 进行斗争 以在预构建步骤中正确转义美元符号 目标是提供一个变量名作为文字 VS 不应该尝试处理变量名 The 文档 https msdn microsoft com en us library bb38
  • Angular 子路由不起作用并将我重定向到同一页面

    我尝试过改变我的route到目前为止我没有发现任何问题 但是 如果您发现任何缺陷 请告诉我 我也会尝试查找任何类型错误并仔细检查我的组件 到目前为止我还没有找到 但请再次告诉我 我尝试输入我的路线 URL 它可以工作 但显示的是相同的页面
  • 使用 Jackson JsonFormat 模式自定义日期字符串

    Summary 我正在尝试解析日期 例如25 Sep 17因此简单日期格式 https docs oracle com javase 8 docs api java text SimpleDateFormat html注释似乎是 JsonF
  • 将标头附加到 Rspec 控制器测试

    我正在尝试为我的控制器编写测试 该控制器接收来自外部服务的请求 到目前为止 这是我的测试 describe ApplyController do context when valid do let parameters do file Fi
  • 在 Python 中使用 Selenium 进行导航并使用 BeautifulSoup 进行抓取

    好的 这就是我想要实现的目标 调用带有动态过滤搜索结果列表的 URL 点击第一个搜索结果 5 页 抓取标题 段落和图像 并将它们作为 json 对象存储在单独的文件中 例如 Title 单个条目的标题元素 Content 各个条目的 DOM
  • 自定义键盘中断处理程序

    我正在尝试编写一个简单的程序 将标准键盘中断替换为自定义的键盘中断 以减少变量 但是 如果不调用旧处理程序 它就无法工作 这是我的中断处理程序 handler proc push ax push di dec EF pushf when t
  • res.send 和 res.render 调用

    我试图确定是否可以同时调用 res send data 和 res render reports 为了进一步详细解释 当我路由到 reports 时 首先在服务器端对返回 json 数据的 API 进行 REST 调用 现在我希望在客户端上
  • 我有 12000 个已知 URL,用 Python 抓取它们的最快方法是什么?

    因此 我有一个从数据库中提取的 URL 列表 我需要抓取并解析每个 URL 的 JSON 响应 某些 URL 返回 null 而其他 URL 返回发送到 csv 文件的信息 我目前正在使用Scrapy 但是抓取这12000个URL大约需要4
  • 将 Double.NaN 与其自身进行比较

    我一直试图找出为什么这两个操作返回不同的值 Double NaN Double NaN回报false Double NaN Equals Double NaN 回报true 我有answer https stackoverflow com
  • TypeScript 中的通用类型反射

    我可以确定泛型类型吗T在以下场景中 class MyClass constructor GenericMethod
  • 当项目数等于列数时,chrome 和 safari 渲染 css 列的方式不同

    我有一个目录列表 它使用 CSS 列 但在 Chrome 和 Safari 中的行为有所不同 目录的每个部分都有一个包装器 将列表排列成两列 我已经有了 CSS 所以 Chrome 会按照我想要的方式呈现它 在 Safari 中 第二列中的
  • 使用 $in 和 $nin 进行查询不使用索引

    当将属性与 in 和 nin 进行匹配时 Mongo 无法正确使用索引 如果仅使用 in 则索引会利用这一点 db assets find tags in blah explain cursor BtreeCursor tags 1 isM
  • Jasper 报告迭代数组列表[重复]

    这个问题在这里已经有答案了 如何创建将在 Jasper 报告中作为参数传递的详细信息部分中的数组列表进行迭代的报告 这可能吗 我搜索并找到了必须添加 ArrayList 作为数据源的解决方案 我怎么做 Regards 您可以将 ArrayL
  • 从 MySQL 中的列表字符串中获取单个项目

    给定以下代表可能列表的字符串 我如何获取指定索引处的项目n 1 2 3 4 5 word1 word2 word3 pipe delimited list 此功能的可能原因是 从 GROUP CONCAT 输出中提取特定元素 从 SET 列
  • 在 Spark 中读取 XML

    我正在尝试使用spark xml jar 读取pyspark 中的xml 嵌套xml df sqlContext read format com databricks spark xml option rowTag hierachy loa
  • Scala:为什么不能编译?

    Given class Foo T def get T class Bar class FooBar extends Foo Bar def get new Bar object Baz def something T U lt Foo T
  • symfony2:如何从模板访问服务

    如果我创建了一个服务 有没有办法从 twig 访问它 而不需要创建 twig extension 您可以将服务设置为一个树枝全局变量config yml e g app config config yml twig globals your
  • 自定义 UINavigationBar 适用于模拟器,但不适用于发布版本

    我希望能够深入了解过去几个小时以来我一直在努力解决的问题 我有一个正在配置的自定义 UINavigationBarapplication DidFinishLaunchingWithOptions通过调用以下方法 UINavigationB
  • 异步函数外部堆栈上下文

    有时代码想知道特定函数 或子函数 是否正在运行 例如 node js 有domains https nodejs org api domain html它也适用于异步内容 不确定这是否包括异步函数 一些简单的代码来解释我需要什么 如下所示