ChatGPT解决这个技术问题 Extra ChatGPT

同步调用异步 Javascript 函数

首先,这是一个非常具体的案例,故意以错误的方式将异步调用改造成一个非常同步的代码库,该代码库有数千行长,而且时间目前无法进行更改以“做”对的。”它伤害了我的每一根纤维,但现实和理想往往无法融合。我知道这很糟糕。

好的,那不碍事了,我该怎么做才能做到:

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

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

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

根本不可能让浏览器阻止并等待。他们只是不会这样做。
javascript dosent 在大多数浏览器上都有阻塞机制...您需要创建一个回调,当异步调用完成以返回数据时调用该回调
您正在寻求一种方法来告诉浏览器“我知道我只是告诉您异步运行之前的函数,但我不是真的故意的!”。为什么你甚至会期望这是可能的?
感谢丹的编辑。我并不是严格意义上的粗鲁,但你的措辞更好。
@RobertC.Barth 现在也可以使用 JavaScript。异步等待功能尚未在标准中获得批准,但计划在 ES2017 中。有关更多详细信息,请参阅下面的答案。

5
5 revs, 2 users 97% user1106925

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

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

“我需要一个具体的例子来说明如何让它阻塞......而不冻结 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);

是的,我知道如何正确地做到这一点,我需要知道如何/是否由于所述的具体原因而不能正确地做到这一点。关键是我不想离开 doSomething() 直到 myAsynchronousCall 完成对回调函数的调用。 Bleh,这不可能,我怀疑,我只是需要互联网收集的智慧来支持我。谢谢你。 :-)
@RobertC.Barth:是的,不幸的是,您的怀疑是正确的。
是我还是只有“正确完成”的版本才有效?问题包括一个返回调用,在此之前应该有一些等待异步调用完成的东西,这个答案的第一部分没有涵盖......
@Leonardo:这是问题中调用的神秘函数。基本上它代表任何异步运行代码并产生需要接收的结果的东西。所以它可能就像一个 AJAX 请求。您将 callback 函数传递给 myAsynchronousCall 函数,该函数执行异步操作并在完成时调用回调。 Here's a demo.
我总是遇到的问题是 doSomething() 通常是整个程序。套用 OP 的话,期望理论编程来反映现实是徒劳的。
J
John

Async functions 是一项功能 in ES2017,它通过使用 promises(一种特定形式的异步代码)和 await 关键字使异步代码看起来同步。还要注意代码示例下方的关键字 async 位于 function 关键字前面,它表示 async/await 函数。如果没有在以 async 关键字为前缀的函数中,await 关键字将不起作用。由于目前没有例外,这意味着没有顶级等待将起作用(顶级等待意味着任何函数之外的等待)。虽然有一个proposal for top-level await

ES2017 于 2017 年 6 月 27 日被批准(即最终确定)为 JavaScript 标准。异步等待可能已经在您的浏览器中运行,但如果不是,您仍然可以使用 babeltraceur 之类的 javascript 转译器使用该功能。 Chrome 55 完全支持异步功能。因此,如果您有更新的浏览器,您可以尝试下面的代码。

有关浏览器兼容性,请参阅 kangax's es2017 compatibility table

这是一个名为 doAsync 的异步等待函数示例,它需要三个一秒的暂停,并打印每次暂停后与开始时间的时间差:

函数 timeoutPromise (time) { return new Promise(function (resolve) { setTimeout(function () { resolve(Date.now()); }, time) }) } function doSomethingAsync () { return timeoutPromise(1000); } 异步函数 doAsync () { var start = Date.now(), time;控制台.log(0);时间 = 等待 doSomethingAsync(); console.log(时间 - 开始);时间 = 等待 doSomethingAsync(); console.log(时间 - 开始);时间 = 等待 doSomethingAsync(); console.log(时间 - 开始); } doAsync();

当 await 关键字放在 promise 值之前(在这种情况下,promise 值是函数 doSomethingAsync 返回的值),await 关键字将暂停函数调用的执行,但它不会暂停任何其他函数,它将继续执行其他代码,直到 promise 解决。在 promise 解决之后,它会解开 promise 的值,你可以认为 await 和 promise 表达式现在被解开的值替换。

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

函数 timeoutPromise (time) { return new Promise(function (resolve) { setTimeout(function () { resolve(Date.now()); }, time) }) } function doSomethingAsync () { return timeoutPromise(1000); } // 这会依次调用每个 promise 返回函数 async function doAsync () { var response = []; var start = Date.now(); // 每个索引都是一个 promise 返回函数 var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync]; for(var i = 0; i < promiseFuncs.length; ++i) { var promiseFunc = promiseFuncs[i]; response.push(await promiseFunc() - 开始);控制台日志(响应); } // 对响应做一些事情,响应是来自已解决的承诺的值数组。返回响应 } doAsync().then(function (response) { console.log(response) })

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

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

// 没有变化函数 timeoutPromise (time) { return new Promise(function (resolve) { setTimeout(function () { resolve(Date.now()); }, time) }) } // 没有变化函数 doSomethingAsync () {返回超时承诺(1000); } // 这个函数几乎同时调用异步承诺返回函数 async function doAsync () { var start = Date.now(); // 我们现在使用 Promise all 来等待所有的 Promise 解决 var response = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);返回responses.map(x=>x-start); } // 没有变化 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 你在 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 没有等待或返回,因此它不会传播错误 doErrorAsync(); return "finished unpropogatedError 成功"; } unpropogatedError().then(log).catch(logErr) 异步函数handledError () { var start = Date.now();尝试 { console.log((await doErrorAsync()) - 开始); console.log("过去的错误"); } catch (e) { console.log("在 catch 我们处理了错误"); } return "完成handledError成功"; }handledError().then(log).catch(logErr) // 错误如何传播到链式 catch 方法的示例 async function propogatedError () { var start = Date.now(); var time = await doErrorAsync() - 开始; console.log(时间 - 开始);返回“成功完成propogatedError”; } // 这是打印 propogatedError 错误的内容。 propogatedError().then(log).catch(logErr)

如果您前往 here,您可以看到即将推出的 ECMAScript 版本的已完成提案。

仅可用于 ES2015 (ES6) 的替代方法是使用包装生成器函数的特殊函数。生成器函数有一个 yield 关键字,可以用来复制 await 关键字和周围的函数。 yield 关键字和生成器函数更通用,可以做更多的事情,而不是 async await 函数所做的事情。如果您想要一个可用于复制异步等待的生成器函数包装器,我会查看 co.js。顺便说一句,co 的函数很像异步等待函数返回一个承诺。老实说,虽然此时浏览器兼容性对于生成器函数和异步函数大致相同,所以如果你只想要异步等待功能,你应该使用不带 co.js 的异步函数。(我建议只使用 async/await 它是在支持上述删除线的大多数环境中得到相当广泛的支持。)

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


这是一个很好的答案,但对于原始海报问题,我认为它所做的只是将问题提升一个级别。假设他将 doSomething 变成了一个带有 await 内部的异步函数。该函数现在返回一个承诺并且是异步的,因此无论调用该函数,他都必须重新处理相同的问题。
@dpwrussell 这是真的,代码库中有大量异步函数和承诺。解决 Promise 蔓延到所有事情的最佳方法就是编写同步回调,除非您执行像 twitter.com/sebmarkbage/status/941214259505119232 这样我不推荐的非常奇怪和有争议的事情,否则无法同步返回异步值。我将在问题的末尾添加一个编辑,以更全面地回答所提出的问题,而不仅仅是回答标题。
这是一个很好的答案+1,但按原样编写,我看不出这比使用回调更简单。
@AltimusPrime 这确实是一个见仁见智的问题,但是错误处理比回调有了很大改进,您始终可以直接使用 Promise,而无需 async/await,这与回调基本相同,但具有更好的错误处理。当你需要将回调传递给函数以在函数的生命周期内多次执行时,回调会胜过承诺。回调甚至不必是异步的。随着时间的推移,Promise 最适合单个值。如果您真的想了解整个价值观,您应该阅读 kriskowal 的 GTOR。
@AltimusPrime 如果随着时间的推移需要多个值,您可以使用 Streams 和 Async Iterables,您可以将这些与 async/await 函数与 for await 语句一起使用,例如 for await (const item of asyncIterable) { }itemasyncIterable 是变量,其余的是关键字。相关链接:Kris Kowal's GTORasyncIterable proposal repo
M
Matt Taylor

看看 JQuery 承诺:

http://api.jquery.com/promise/

http://api.jquery.com/jQuery.when/

http://api.jquery.com/deferred.promise/

重构代码:

var dfd = new jQuery.Deferred();


    function callBack(data) {
       dfd.notify(data);
    }

    // do the async call.
    myAsynchronousCall(param1, callBack);

    function doSomething(data) {
     // do stuff with data...
    }

    $.when(dfd).then(doSomething);

+1 这个答案,这是正确的。但是,我会将带有 dfd.notify(data) 的行更新为 dfd.resolve(data)
这是代码给人一种同步错觉的情况,实际上不是异步的吗?
Promise 是 IMO 组织良好的回调 :) 如果你需要一个异步调用,比如说一些对象初始化,那么 Promise 会产生一点不同。
承诺不同步。
m
meustrus

可以强制 NodeJS 中的异步 JavaScript 与 sync-rpc 同步。

不过,它肯定会冻结你的 UI,所以当谈到是否有可能走你需要走的捷径时,我仍然是反对者。不可能在 JavaScript 中暂停 One And Only 线程,即使 NodeJS 有时允许您阻止它。在您的承诺解决之前,任何回调、事件、任何异步操作都无法处理。因此,除非您的读者遇到像 OP 这样的不可避免的情况(或者,在我的情况下,正在编写一个没有回调、事件等的美化 shell 脚本),否则不要这样做!

但您可以这样做:

./calling-file.js

var createClient = require('sync-rpc');
var mySynchronousCall = createClient(require.resolve('./my-asynchronous-call'), 'init data');

var param1 = 'test data'
var data = mySynchronousCall(param1);
console.log(data); // prints: received "test data" after "init data"

./my-asynchronous-call.js

function init(initData) {
  return function(param1) {
    // Return a promise here and the resulting rpc client will be synchronous
    return Promise.resolve('received "' + param1 + '" after "' + initData + '"');
  };
}
module.exports = init;

限制:

这些都是通过滥用 require('child_process').spawnSync 实现 sync-rpc 的结果:

这在浏览器中不起作用。函数的参数必须是可序列化的。您的参数将传入和传出 JSON.stringify,因此函数和诸如原型链之类的不可枚举属性将丢失。


这个答案直接解决了问题的核心。我也许可以将此应用于我的一个特定案例。
必须使用 lib (Quasar) 的异步代码来填充同步 Webpack 配置 - 所以我显然无法重写它们 - 你救了我!非常感谢!
G
George Vinokhodov

http://taskjs.org/ 有一个很好的解决方法

它使用对 javascript 来说是新的生成器。所以目前大多数浏览器都没有实现它。我在 Firefox 中对其进行了测试,对我来说这是包装异步函数的好方法。

这是来自项目 GitHub 的示例代码

var { Deferred } = task;

spawn(function() {
    out.innerHTML = "reading...\n";
    try {
        var d = yield read("read.html");
        alert(d.responseText.length);
    } catch (e) {
        e.stack.split(/\n/).forEach(function(line) { console.log(line) });
        console.log("");
        out.innerHTML = "error: " + e;
    }

});

function read(url, method) {
    method = method || "GET";
    var xhr = new XMLHttpRequest();
    var deferred = new Deferred();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 400) {
                var e = new Error(xhr.statusText);
                e.status = xhr.status;
                deferred.reject(e);
            } else {
                deferred.resolve({
                    responseText: xhr.responseText
                });
            }
        }
    };
    xhr.open(method, url, true);
    xhr.send();
    return deferred.promise;
}

e
easrng

你想要的现在实际上是可能的。如果你可以在 service worker 中运行异步代码,在 web worker 中运行同步代码,那么你可以让 web worker 向 service worker 发送同步 XHR,当 service worker 做异步事情时,web worker 的线程将等待。这不是一个好方法,但它可以工作。


这是一种干净的方法,仍然不推荐使用 coruse :)
后端/node.js 解决方案似乎仍然需要它..
M
Maxim Kulikov

在 Node.js 中,可以编写实际调用异步操作的同步代码。 node-fibers 允许这样做。它是作为 npm 模块提供的第 3 方本机扩展。它实现了纤程/协程,因此当特定纤程被阻塞等待异步操作时,整个程序事件循环不会阻塞 - 另一个纤程(如果存在)继续其工作。

使用纤维,您的代码将如下所示:

var Fiber = require('fibers');

function doSomething() {
  var fiber = Fiber.current;

  function callBack(data) {
    fiber.run(data);
  }

  myAsynchronousCall(param1, callBack);

  // execution blocks here
  var data = Fiber.yield();
  return data;
}

// The whole program must be wrapped with Fiber
Fiber(function main() {

  var data = doSomething();
  console.log(data);

}).run();

请注意,您应该避免使用它并改用 async/await。请参阅下面的项目自述文件 https://github.com/laverdet/node-fibers 中的注释:

过时的注意——这个项目的作者建议你尽可能避免使用它。该模块的原始版本在 2011 年初针对 nodejs v0.1.x,当时服务器上的 JavaScript 看起来有很大不同。从那时起,async/await、Promises 和 Generators 被标准化,整个生态系统也朝着这个方向发展。我会尽可能地继续支持更新版本的 nodejs,但是 v8 和 nodejs 是非常复杂和动态的平台。不可避免地有一天,这个库会突然停止工作,没有人能对此做任何事情。我要感谢所有使用光纤的用户,您多年来的支持对我来说意义重大。


'node-fibers' 的作者建议您尽可能避免使用它
@MuhammadInaamMunir 是的,答案中提到了
n
ninjagecko

人们可能不会考虑的一件事:如果您控制异步函数(其他代码段依赖),并且它所采用的代码路径不一定是异步的,您可以通过创建使其同步(而不破坏其他代码段)一个可选参数。

目前:

async function myFunc(args_etcetc) {
    // you wrote this
    return 'stuff';
}

(async function main() {
    var result = await myFunc('argsetcetc');
    console.log('async result:' result);
})()

考虑:

function myFunc(args_etcetc, opts={}) {
    /*
        param opts :: {sync:Boolean} -- whether to return a Promise or not
    */
    var {sync=false} = opts;
    if (sync===true)
        return 'stuff';
    else
        return new Promise((RETURN,REJECT)=> {
            RETURN('stuff');
        });
}


// async code still works just like before:
(async function main() {
    var result = await myFunc('argsetcetc');
    console.log('async result:', result);
})();
// prints: 'stuff'

// new sync code works, if you specify sync mode:
(function main() {
    var result = myFunc('argsetcetc', {sync:true});
    console.log('sync result:', result);
})();
// prints: 'stuff'

当然,如果异步函数依赖于固有的异步操作(网络请求等),这将不起作用,在这种情况下,努力是徒劳的(没有有效地等待空闲旋转)。

同样,根据传入的选项返回值或 Promise 也相当难看。

(“如果它不使用异步构造,为什么我会编写一个异步函数?”有人可能会问?也许函数的某些模式/参数需要异步性而其他不需要,并且由于代码重复,您需要一个整体块而不是在不同的函数中分离模块化的代码块......例如,参数可能是 localDatabase (不需要等待)或 remoteDatabase (需要等待)。如果你尝试这样做,你可能会出现运行时错误{sync:true} 在远程数据库上。也许这种情况表明存在另一个问题,但你去吧。)


p
pont

使用 Node 16 的工作线程实际上使这成为可能,以下示例主线程正在运行异步代码,而工作线程正在同步等待它。

这不是很有用,但它至少通过同步等待异步代码模糊地完成了原始问题。

const {
    Worker, isMainThread, parentPort, receiveMessageOnPort
} = require('worker_threads');
if (isMainThread) {
    const worker = new Worker(__filename);
    worker.on('message', async () => {
        worker.postMessage(await doAsyncStuff());
    });
} else {
    console.log(doStuffSync());
}

function doStuffSync(){
    parentPort.postMessage({fn: 'doStuff'});
    let message;
    while (!message) {
        message = receiveMessageOnPort(parentPort)
    }
    return message;
}

function doAsyncStuff(){
    return new Promise((resolve) => setTimeout(() => resolve("A test"), 1000));
}

d
dangtrunganh

Promise 的这种能力包括以下同步操作的两个关键特性(或者 then() 接受两个回调)。得到结果后,调用 resolve() 并传递最终结果。如果出现错误,请调用reject()。

这个想法是结果通过 .then() 处理程序链传递。

const synchronize = (() => {
    let chain = Promise.resolve()
    return async (promise) => {
        return chain = chain.then(promise)
    }
})()

A
Aleksandr Chernyi
let result;
async_function().then(r => result = r);
while (result === undefined) // Wait result from async_function
    require('deasync').sleep(100);

您的答案可以通过额外的支持信息得到改进。请edit添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。您可以找到有关如何写出好答案的更多信息in the help center
虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高答案的长期价值。您可以在帮助中心找到更多关于如何写出好答案的信息:stackoverflow.com/help/how-to-answer。祝你好运🙂
D
Dabbas

您也可以将其转换为回调。

function thirdPartyFoo(callback) {    
  callback("Hello World");    
}

function foo() {    
  var fooVariable;

  thirdPartyFoo(function(data) {
    fooVariable = data;
  });

  return fooVariable;
}

var temp = foo();  
console.log(temp);

好吧,如果 thirdPartyFoo 正在执行一些异步操作,那么您将在 temp always 中得到 null
e
eragon512

如果您稍微调整一下要求,您希望实现的想法就可以实现

如果您的运行时支持 ES6 规范,则可以使用以下代码。

关于async functions的更多信息

async function myAsynchronousCall(param1) {
    // logic for myAsynchronous call
    return d;
}

function doSomething() {

  var data = await myAsynchronousCall(param1); //'blocks' here until the async call is finished
  return data;
}

Firefox 给出错误:SyntaxError: await is only valid in async functions and async generators。更不用说 param1 没有定义(甚至没有使用)。