JavaScript - 同步等待异步操作(睡眠)

时间:2017-01-25 01:52:30

标签: javascript asynchronous promise async-await

我知道这里有很多次被问过,很多时候回答这不是应该怎样做的方式,而是再一次:)

是否有可能以某种方式调用异步函数(例如timer / ajax调用),基本上是常见的异步任务并同步等到它结束而没有100%的CPU使用率并阻止浏览器?

简单的回答就足够了 - 是或否。如果不是我必须根据“异步方式”中的异步操作编写所有代码,否则,它会更好;)

想象一下:

updateCSS("someurl.css")

function updateCSS(url) {

   var css = getCachedResource(url);

   css = css.replace(/regexp/gm, function(curUrl) {
     base64 = atob(getCachedResource(curUrl))
     return "data:image;base64," + base64
   })

}

function getCachedResource(url) {
  
  //...
  
  if (cached) {
    
    return cached
    
  } else {
    
    // doajax...    
    // watfor
    
    return loaded
  }
  
}

当然,我可以让它异步,但代码会......非常糟糕。而且,我必须使调用此函数的所有函数都异步。你能看到简单的解决方案吗我会说阻塞是最简单的。

由于

7 个答案:

答案 0 :(得分:1)

您希望代码“休眠”,直到“异步功能(例如定时器/ ajax调用)...结束而没有100%的CPU使用率”。这正是async/await所做的。

其他答案和评论都强调你的短语“同步等待”,这在JS中意味着阻止,但我理解你只是想避免必须重构你的代码“异步方式”。你想直截了当地写,而不是将所有内容都构造成无数的回调。

要使用async / await,首先必须接受承诺 - 异步JavaScript的空间/时间。然后,您需要一个像Chrome或Firefox Developer Edition这样的现代浏览器,或者使用Babel js。

来转换您的代码

接下来,你想看看fetch,这是承诺回归的现代方式来做ajax。

现在你拥有了所需的一切。这是一个适用于上述浏览器的示例:

async function fetchy(...args) {
  let response = await fetch(...args);
  if (!response.ok) throw new Error((await response.json()).error_message);
  return response;
}

async function showUser(user) {
  let url = `https://api.stackexchange.com/2.2/users/${user}?site=stackoverflow`;
  let item = (await (await fetchy(url)).json()).items[0];
  console.log(JSON.stringify(item, null, 2));

  let blob = await (await fetchy(item.profile_image)).blob();
  img.src = URL.createObjectURL(blob);
}

input.oninput = async e => await showUser(e.target.value);
showUser(input.value);
<input id="input" type="number" value="918910"></input><br>
<img id="img"></img>
<pre id="pre"></pre>

仅供参考,fetchy是理解fetch错误处理的助手。

答案 1 :(得分:1)

,即使使用100%的cpu或阻止的浏览器,也无法做到这一点。事情已经精确设计,以防止你这样做。

这使得所有异步代码都有毒,这意味着调用异步函数(基于promise或回调)的所有代码本身必须是异步的。

关于可怕的代码:Promise链和async/await用于救援,它们可以帮助您编写具有更好语法的异步代码。 不要害怕,请使用async/await,尤其是如果您已经使用过转发器。

答案 2 :(得分:0)

正如其他人所指出的那样,简短的答案是否定的:Javascript在大多数使用它的环境中都是单线程的。你要做的是在getCachedResource()的另一个回调中处理你的CSS

这样的事情:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">>
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>

    <item name="toolbarStyle">@style/AppTheme.Toolbar</item>
</style>

看看jQuery“延迟”对象或Javascript承诺,以更完整的方式处理回调

答案 3 :(得分:0)

所以只是总结......

答案是否定的。永远不可能阻止该函数或任何其他代码,并等待异步操作完成。 JavaScript VM没有直接支持它的机制。

特别是在浏览器环境中,无法操作JavaScript虚拟机事件循环(因为可以使用Node以及实际上&#34; deasync&#34;库正在执行此操作)。

这是因为这里提到的JavaScript并发模型和事件处理:

https://developer.mozilla.org/cs/docs/Web/JavaScript/EventLoop

它与JavaScript是单线程无关。

另外,请注意,像Promise这样的东西和await / async等新功能只是一种不同的方式,如何编写/管理异步任务的代码。它不是也不会有助于将异步转为同步。

所以不要像我一样花时间试图找到一种方法来做到这一点。而是通过设计异步代码来花费它;)

答案 4 :(得分:0)

简单地说,答案是否定的。

不要这么想。考虑后端性能,而不是在客户端做。

答案 5 :(得分:0)

  

是否有可能以某种方式调用异步函数(例如timer / ajax)   调用),基本上是常见的异步任务并同步等到它   没有100%的CPU使用率和阻止浏览器结束?

没有。您无法在Javascript中“同步等待”异步结果。由于它是单线程和事件驱动的设计,它不会那样工作。

由于关于这个问题的大多数其他谈话/评论都是概念性的,我想我会提供一个答案,说明如何使用适当的异步设计实际解决您的具体问题。

Javascript不提供睡眠能力,而一些耗时的活动结束。因此,您无法将updateCSS()函数设计为具有纯粹的同步接口。相反,您需要在Javascript中使用异步工具来设计一个接口,该接口既适用于缓存资源的情况,也适用于必须通过网络获取资源的情况。

设计的常用方法是设计异步接口,如果资源恰好被缓存,您仍然可以通过异步接口提供服务。然后,您有一个调用者可以使用的接口,无论资源是否被缓存,该接口始终有效。它要求调用者使用异步接口。

实际上,您可以像getCachedResource()一样进行设计:

function getCachedResource(url) {
  //...
  if (cached) {
    // returns a promise who's resolved value is the resource
    return Promise.resolve(cached);
  } else {
    // returns a promise who's resolved value is the resource
    return doAjax(...);
  }
}

此处getCachedResource()始终返回一个承诺,无论该值是否被缓存。因此,调用者总是使用promise接口,如下所示:

getCachedResource(...).then(function(result) {
    // use the result here
});

如果结果恰好被缓存,则会立即调用.then()处理程序(在下一个tick中)。如果通过网络检索结果,则只要结果可用,就会调用.then()处理程序。调用者不需要担心资源的检索位置和方式 - 他们有一个接口来获取资源。

updateCSS()函数中,您将不得不设计在getCachedResource()中使用异步接口。因为您当前的设计在css.replace()中使用同步回调,所以您必须重新考虑这一点以使异步检索适应那里。这是如何做到这一点的一种方法。

function updateCSS(url) {
   return getCachedResource(url).then(function(css) {
       // preflight the replacements we need to do so we know what to fetch
       var urls = [];
       css.replace(/regexp/gm, function(curUrl) {
           urls.push(curUrl);
       });
       // now load all the resources
       return Promise.all(urls.map(getCachedResource)).then(function(resources) {
           // now have all the resources, in proper order
           var index = 0;
           css = css.replace(/regexp/gm, function(curUrl) {
               return resources[index++];
           });
           // final resolved value is the fully loaded css
           return css;
       })
   });
}

此处的方案是运行一个虚拟.replace(),它不会保留结果以生成您需要加载的网址列表。根据您的正则表达式,也可以使用.match().exec()来完成此操作。然后,一旦你有了要获取的URL列表,就去获取它们。如果你拥有所有这些,那么你可以运行.replace() for real,因为你有可用于同步.replace()回调的资源。

然后可以这样使用:

updateCss(someCSSURL).then(function(css) {
    // use the CSS here
}).catch(function(err) {
    // process error here
});

答案 6 :(得分:0)

我喜欢所有其他答案,我理解为什么您不能使异步任务同步。我被带到这里是想弄清楚如何 await 让 FileReader 对象完成读取,以便我可以使用同一个 FileReader 对象读取多个文件。

我克服的个人问题:FileReader 对象不返回承诺,因此您不能等待

我创建了一个递归函数,该函数将在循环中运行以执行第一次读取,并检查 .loadend 事件何时命中,然后移至下一个文件。对于那些批评者来说,是的,这可能是不好的做法,但确实有效。

回到您的问题,ajax 请求是异步的,就像我的示例一样,它不返回承诺,因此您不能使用等待。经过一番挖掘,我发现了一个 stackoverflow 帖子,它可能以实用的方式解决我们的两个问题:

How to await the ajax request?

您可以创建一个函数来获取 url 并返回 ajax 调用。这可以被 awaited,因此您可以使用 .then(doSomethingAfter()) 和所有 Promise 可以做的很酷的事情。试用后,我会告诉您 FileReader 的工作原理。

~~~~编辑~~~~

这是我的代码:

function getFileResolve(reader, files, currentNum) {
    try {
        var file = files[currentNum];
        return reader.readAsText(file)
    } catch(err) {
        console.log(err)
    }
};

function main() {
    $('#imported-files').on('change', function() {
        var files = this.files,
            reader = new FileReader(),
            currentFileNum = 0;

        reader.onload = function(e) {
            console.log(e.target.result);
            currentFileNum++;

            getFileResolve(reader, files, currentFileNum);
        }
        
        getFileResolve(reader, files, currentFileNum);
    })
};

$(document).ready(main);

ajax 调用可以被返回,并且从那里可以被视为一个承诺。 FileReader 对象有一个 onloaded 事件,如前所述,因此可以为您调用下一个文件,并且它自己是递归的。希望这可以帮助那里的任何人。 #promises_are_hard