2

我正在开发 Grails 应用程序。我想要做的是锁定请求/响应,创建一个承诺,并让其他人解决它,即代码中的其他地方,然后刷新响应

我发现真正奇怪的是 Promise promise = task {} 接口没有类似于 resolve 或类似的方法。

我需要锁定响应,直到有人解决了承诺,这是在开发模式下设置的全局/静态属性。

Promise 接口: http: //grails.org/doc/latest/api/grails/async/Promise.html

我查看了 GPars 文档,在那里找不到任何类似于解析方法的内容。

如何创建一个承诺,锁定响应或请求,然后在有人解决它时刷新响应?

4

2 回答 2

4

您可以调用get()将阻塞的承诺,直到任务完成为止,但我想那不是您想要的。您想要的似乎等同于 GPars DataflowVariable

http://gpars.org/1.0.0/javadoc/groovyx/gpars/dataflow/DataflowVariable.html

这允许使用左移运算符来解析来自另一个线程的值。目前无法通过 Grails 直接使用左移运算符,但由于 Grails 的 promise API 只是 GPars 之上的一个层,这可能可以通过直接使用 GPars API 来完成,例如:

 import org.grails.async.factory.gpars.*
 import groovyx.gpars.dataflow.*
 import static grails.async.Promise.*

 def myAction() {
    def dataflowVar = new DataflowVariable()
    task {
       // do some calculation and resolve data flow variable
       def expensiveData = ...
       dataflowVar << expensiveData
    }
    return new GParsPromise(dataflowVar)        
 }
于 2014-07-02T20:14:31.423 回答
0

我花了相当长的时间来解决这个问题并得到一个有效的答案。

我必须说,看起来 Grails 要让它正常工作还有很长的路要走。

任务 { }

将始终立即执行,因此在 dispatch() 或任何有问题的调用之前不会暂停调用。

试试这个看看:

    public def test() {
            def dataflowVar = new groovyx.gpars.dataflow.DataflowVariable()

            task {
                    // do some calculation and resolve data flow variable
                    println '1111111111111111111111111111111111111111111111111111'
                    //dataflowVar << expensiveData
            }

            return new org.grails.async.factory.gpars.GparsPromise(dataflowVar);
    }

如果你想知道这是为了什么,它是让 grails 中的 lesscss 自动刷新,当你在 less 中使用 import 语句时,这是一个问题。当文件被触动时,lesscss 编译器会触发重新编译,只有完成后才会响应客户端。

在客户端,我有一些 javascript 在这里使用刷新操作不断替换最后一个:

在我的控制器中:

    /**
     * Refreshes link resources. refresh?uri=/resource/in/web-app/such/as/empty.less
     */
    public def refresh() {
            return LessRefresh.stackRequest(request, params.uri);
    }

为此编写的一个类:

import grails.util.Environment
import grails.util.Holders

import javax.servlet.AsyncContext
import javax.servlet.AsyncEvent
import javax.servlet.AsyncListener
import javax.servlet.http.HttpServletRequest

/**
 * @Author SecretService
 */
class LessRefresh {
        static final Map<String, LessRefresh> FILES = new LinkedHashMap<String, LessRefresh>();

        String file;
        Boolean touched
        List<AsyncContext> asyncContexts = new ArrayList<AsyncContext>();
        String text;

        public LessRefresh(String file) {
                this.file = file;
        }

        /** Each request will be put on hold in a stack until dispatchAll below is called when the recompilation of the less file finished **/
        public static AsyncContext stackRequest(HttpServletRequest request, String file) {
                if ( !LessRefresh.FILES[file] ) {
                        LessRefresh.FILES[file] = new LessRefresh(file);
                }

                return LessRefresh.FILES[file].handleRequest(request);
        }

        public AsyncContext handleRequest(HttpServletRequest request) {
                if ( Environment.current == Environment.DEVELOPMENT ) {

                        // We only touch it once since we are still waiting for the less compiler to finish from previous edits and recompilation
                        if ( !touched ) {
                                touched = true
                                touchFile(file);
                        }

                        AsyncContext asyncContext = request.startAsync();

                        asyncContext.setTimeout(10000)

                        asyncContexts.add (asyncContext);

                        asyncContext.addListener(new AsyncListener() {

                                @Override
                                void onComplete(AsyncEvent event) throws IOException {
                                        event.getSuppliedResponse().writer << text;
                                }

                                @Override
                                void onTimeout(AsyncEvent event) throws IOException {

                                }

                                @Override
                                void onError(AsyncEvent event) throws IOException {

                                }

                                @Override
                                void onStartAsync(AsyncEvent event) throws IOException {

                                }
                        });

                        return asyncContext;
                }

                return null;
        }

        /** When recompilation is done, dispatchAll is called from LesscssResourceMapper.groovy **/
        public void dispatchAll(String text) {
                this.text = text;

                if ( asyncContexts ) {

                        // Process all
                        while ( asyncContexts.size() ) {

                                AsyncContext asyncContext = asyncContexts.remove(0);

                                asyncContext.dispatch();
                        }

                }

                touched = false;
        }

        /** A touch of the lessfile will trigger a recompilation **/
        int count = 0;
        void touchFile(String uri) {
                if ( Environment.current == Environment.DEVELOPMENT ) {
                        File file = getWebappFile(uri);

                        if (file && file.exists() ) {
                                ++count;

                                if ( count < 5000 ) {
                                        file << ' ';
                                }
                                else {
                                        count = 0

                                        file.write( file.getText().trim() )
                                }
                        }
                }
        }

        static File getWebappFile(String uri) {
                new File( Holders.getServletContext().getRealPath( uri ) )
        }

}

在 lesscsss-recources 插件的 LesscssResourceMapper.groovy 中:

    ...
    try {
            lessCompiler.compile input, target
            // Update mapping entry
            // We need to reference the new css file from now on
            resource.processedFile = target
            // Not sure if i really need these
            resource.sourceUrlExtension = 'css'
            resource.contentType = 'text/css'
            resource.tagAttributes?.rel = 'stylesheet'

            resource.updateActualUrlFromProcessedFile()

            // ==========================================
            // Call made here!
            // ==========================================
            LessRefresh.FILES[resource.sourceUrl.toString()]?.dispatchAll( target.getText() );

    } catch (LessException e) {
            log.error("error compiling less file: ${originalFile}", e)
    }
    ...

在 index.gsp 文件中:

<g:set var="uri" value="${"${App.files.root}App/styles/empty.less"}"/>
<link media="screen, projection" rel="stylesheet" type="text/css" href="${r.resource(uri:uri)}" refresh="${g.createLink(controller:'home', action:'refresh', params:[uri:uri])}" resource="true">

JavaScript 方法 refreshResources 替换之前的链接 href=...

    /**
     * Should only be used in development mode
     */
    function refreshResources(o) {
            o || (o = {});

            var timeoutBegin      = o.timeoutBegin      || 1000;
            var intervalRefresh   = o.intervalRefresh   || 1000;
            var timeoutBlinkAvoid = o.timeoutBlinkAvoid || 400 ;
            var maxErrors         = o.maxErrors         || 200 ;

            var xpath = 'link[resource][type="text/css"]';

            // Find all link[resource]
            $(xpath).each(function(i, element) {
                    refresh( $(element) );
            });

            function refresh(element) {

                    var parent     = element.parent();
                    var next       = element.next();
                    var outer      = element.clone().attr('href', '').wrap('<p>').parent().html();
                    var uri        = element.attr('refresh');
                    var errorCount = 0;

                    function replaceLink() {
                            var link = $(outer);

                            link.load(function () {
                                    // The link has been successfully added! Now remove the other ones, then do again

                                    errorCount = 0;

                                    // setTimeout needed to avoid blinking, we allow duplicates for a few milliseconds
                                    setTimeout(function() {
                                            var links = parent.find(xpath + '[refresh="'+uri+'"]');

                                            var i = 0;
                                            // Remove all but this one
                                            while ( i < links.length - 1 ) {
                                                    links[i++].remove();
                                            }

                                            replaceLinkTimeout();

                                    }, timeoutBlinkAvoid );

                            });

                            link.error(function(event, handler) {
                                    console.log('Error refreshing: ' + outer );

                                    ++errorCount;

                                    if ( errorCount < maxErrors ) {
                                            // Load error, it happens. Remove this & redo!
                                            link.remove();

                                            replaceLink();
                                    }
                                    else {
                                            console.log('Refresh: Aborting!')
                                    }

                            });

                            link.attr('href', urlRandom(uri)).get(0);
                            link.insertBefore(next); // Insert just after
                    }

                    function urlRandom(uri) {
                            return uri + "&rand=" + Math.random();
                    }

                    function replaceLinkTimeout() {
                            setTimeout(function() {
                                    replaceLink();
                            }, intervalRefresh ) ;

                    }

                    // Waith 1s before triggering the interval
                    setTimeout(function() {
                            replaceLinkTimeout();
                    }, timeoutBegin);
            }

    };

注释

我不确定为什么没有将 Javascript样式的 promise添加到Grails 堆栈中。你不能在 onComplete 中渲染或类似的东西。渲染,重定向和不可用的东西。

有些东西告诉我,Grails 和 Promises/Futures 还没有。GPars 库的设计似乎没有考虑到稍后要解决的核心功能。至少这样做并不简单。

如果 dispatch() 方法实际上可以用一些参数调用以从解析上下文传递,那就太好了。我可以使用静态属性来解决这个问题。

我可能会继续编写自己的解决方案,并可能为 AsyncContext 类提供更合适的解决方案,但现在,这对我来说已经足够了。

我只是想自动刷新我较少的资源。

呸...

编辑:

我使它支持多个文件。现在已经完成了!

于 2014-07-03T16:03:10.333 回答