同步调用异步 JavaScript 函数

2022-08-30 00:07:34

首先,这是一个非常具体的情况,故意以错误的方式将异步调用改装成一个非常同步的代码库,该代码库长达数千行,并且时间目前无法进行更改以“正确执行”。它伤害了我存在的每一根纤维,但现实和理想往往不会融合在一起。我知道这很糟糕。

好吧,这就说了,我如何做到这一点,以便我可以:

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}

这些示例(或缺少示例)都使用库和/或编译器,这两者对于此解决方案都不可行。我需要一个具体的例子来说明如何在不冻结UI的情况下让它阻塞(例如,在调用回调之前不要离开doSomething函数)。如果这样的事情在JS中是可能的。


答案 1

“不要告诉我我应该如何”以正确的方式“去做”或其他什么”

好吧,但你真的应该以正确的方式去做...或者其他什么

“我需要一个具体的例子来说明如何让它阻塞...不冻结 UI。如果这样的事情在JS中是可能的。

不,不可能在不阻止UI的情况下阻止正在运行的JavaScript。

鉴于缺乏信息,很难提供解决方案,但一种选择可能是让调用函数进行一些轮询以检查全局变量,然后将回调设置为全局变量。data

function doSomething() {

      // callback sets the received data to a global var
  function callBack(d) {
      window.data = d;
  }
      // start the async
  myAsynchronousCall(param1, callBack);

}

  // start the function
doSomething();

  // make sure the global is clear
window.data = null

  // start polling at an interval until the data is found at the global
var intvl = setInterval(function() {
    if (window.data) { 
        clearInterval(intvl);
        console.log(data);
    }
}, 100);

所有这些都假定您可以修改 。我不知道这是否在卡片中。doSomething()

如果可以修改,那么我不知道为什么你不会只是传递一个回调来从另一个回调调用,但我最好在遇到麻烦之前停止。;)doSomething()


哦,到底是什么。你举了一个例子,表明它可以正确地完成,所以我将展示这个解决方案......

function doSomething( func ) {

  function callBack(d) {
    func( d );
  }

  myAsynchronousCall(param1, callBack);

}

doSomething(function(data) {
    console.log(data);
});

由于您的示例包含传递给异步调用的回调,因此正确的方法是传递要从回调调用的函数。doSomething()

当然,如果这是回调唯一要做的事情,你只需直接传递...func

myAsynchronousCall(param1, func);

答案 2

异步函数ES2017 中的一项功能,它通过使用 promise(异步代码的特定形式)和关键字使异步代码看起来同步。另请注意,在下面的代码示例中,关键字前面是表示异步/等待函数的关键字。如果不在使用关键字预先修复的函数中,关键字将无法工作。由于目前没有例外,这意味着没有顶级等待将起作用(顶级等待意味着任何函数之外的等待)。虽然有一个关于顶级等待的建议awaitasyncfunctionawaitasync

ES2017 于 2017 年 6 月 27 日被批准(即最终确定)为 JavaScript 标准。Async await可能已经在您的浏览器中工作,但如果没有,您仍然可以使用javascript转译器(如babeltracer)来使用该功能。Chrome 55完全支持异步功能。因此,如果您有较新的浏览器,则可以尝试以下代码。

请参阅 kangax 的 es2017 兼容性表,了解浏览器兼容性。

下面是一个调用的异步等待函数示例,该函数需要三个一秒的暂停,并在每次暂停后从开始时间开始打印时差:doAsync

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

async function doAsync () {
  var start = Date.now(), time;
  console.log(0);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
}

doAsync();

当 await 关键字放在 promise 值之前(在本例中,promise 值是函数 doSomethingAsync 返回的值)时,await 关键字将暂停执行函数调用,但它不会暂停任何其他函数,它将继续执行其他代码,直到 promise 解析为止。在承诺解析后,它将解开承诺的值,您可以将 await 和 promise 表达式视为现在被该未包装的值所取代。

因此,由于 await 只是暂停等待,然后在执行该行的其余部分之前解开一个值,因此您可以在 for 循环和内部函数调用中使用它,如下面的示例所示,该示例收集数组中等待的时间差并打印出数组。

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this calls each promise returning function one after the other
async function doAsync () {
  var response = [];
  var start = Date.now();
  // each index is a promise returning function
  var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];
  for(var i = 0; i < promiseFuncs.length; ++i) {
    var promiseFunc = promiseFuncs[i];
    response.push(await promiseFunc() - start);
    console.log(response);
  }
  // do something with response which is an array of values that were from resolved promises.
  return response
}

doAsync().then(function (response) {
  console.log(response)
})

async 函数本身返回一个 promise,因此您可以将其用作带有链接的 promise,就像我在上面或在另一个 async await 函数中所做的那样。

上面的函数将在发送另一个请求之前等待每个响应,如果您想同时发送请求,则可以使用Promise.all

// no change
function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

// no change
function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this function calls the async promise returning functions all at around the same time
async function doAsync () {
  var start = Date.now();
  // we are now using promise all to await all promises to settle
  var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);
  return responses.map(x=>x-start);
}

// no change
doAsync().then(function (response) {
  console.log(response)
})

如果 promise 可能被拒绝,则可以将其包装在 try catch 中,或者跳过 try catch 并让错误传播到 async/await 函数 catch 调用。您应该注意不要将承诺错误保留为未处理,尤其是在Node.js中。以下是一些展示错误如何工作的示例。

function timeoutReject (time) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));
    }, time)
  })
}

function doErrorAsync () {
  return timeoutReject(1000);
}

var log = (...args)=>console.log(...args);
var logErr = (...args)=>console.error(...args);

async function unpropogatedError () {
  // promise is not awaited or returned so it does not propogate the error
  doErrorAsync();
  return "finished unpropogatedError successfully";
}

unpropogatedError().then(log).catch(logErr)

async function handledError () {
  var start = Date.now();
  try {
    console.log((await doErrorAsync()) - start);
    console.log("past error");
  } catch (e) {
    console.log("in catch we handled the error");
  }
  
  return "finished handledError successfully";
}

handledError().then(log).catch(logErr)

// example of how error propogates to chained catch method
async function propogatedError () {
  var start = Date.now();
  var time = await doErrorAsync() - start;
  console.log(time - start);
  return "finished propogatedError successfully";
}

// this is what prints propogatedError's error.
propogatedError().then(log).catch(logErr)

如果你去这里,你可以看到即将推出的 ECMAScript 版本的已完成建议。

可以与ES2015(ES6)一起使用的替代方法是使用一个包裹生成器函数的特殊函数。生成器函数具有 yield 关键字,可用于复制具有周围函数的 await 关键字。yield 关键字和生成器函数的用途要通用得多,可以做更多的事情,而不仅仅是异步 await 函数的功能。如果你想要一个可用于复制异步等待的生成器函数包装器,我会检查一下co.js。顺便说一句,co 的函数很像 async await 函数返回一个 promise。老实说,尽管在这一点上,生成器函数和异步函数的浏览器兼容性大致相同,因此,如果您只想要异步等待功能,则应使用异步函数而不使用.js。(我建议只使用 async/await,在大多数支持上述删除线的环境中,它得到了非常广泛的支持。

现在,除了IE之外,对于所有主要当前浏览器(Chrome,Safari和Edge)中的异步功能(截至2017年),浏览器支持实际上非常好。