Dojo Deferreds and Promises

原文: http://dojotoolkit.org/documentation/tutorials/1.10/promises/index.html

版本: Dojo 1.10


Deferreds是一个神奇且功能强大的东西,是一个更伟大的东西Promises的实现。这里,我们将会学习它们的概念,以及其它一些在统一方式下同时使用promises和常规值Dojo's API。

学习这节的基础是要先学习dojo/request和dojo/Deferred知识,和这些接口的基础概念,我们将会介绍一个更加抽象的概念:promises。一个promise是一个对象,它表示一个操作结束后的最终返回值。dojo/promise接口在1.8版本后进行了重大的更新和改良。一个promise有如下特性:

> 可以在这三种状态之一的状态:unfulfilled,resolve,rejected

> 只可以从unfulfilled转变到resolved或unfulfilled转变到rejected状态

> 实现是一个then方法,用来注册提醒状态改变的回调函数

> 回调函数不能改变promise返回的修士

> 一个promise的then方法返回一个新的promise,用来在提供链式结构同时保持初始promise的值不变。

有了这些知识,我们来探究Dojo是如何实现promises的。


Defferred as a Promise

如果你觉得一个promise听起来更像是一个Deferred的话,说明你已经注意到这点了。事实上,dojo/Deferred模块是Dojo的promise接口的主要实现。如下例子:

require(['dojo/request'],function(request) {
    // original is a Deferred
    var original = request.get("users-mangled.json",{
        handleAs: "json"
    });
});
正如之前所说,request.get(和其它Dojo的Ajax帮助函数)返回了一个promise,这个promise就表示从server端请求结束时返回的最终值。最初,它将会是一个unfulfilled状态,然后根据server返回的结果转变为resolved或rejected状态。

我们可以在请求返回的结果promise上通过then方法注册回调函数,但是,我们只能知道then方法的返回值有一个then方法,而并不能涵盖then方法返回值的全部。你可能认为它返回了初始promise,但是它实际上返回了一个实现promise接口的简单的对象。两个最常用的方法是then和cancel。下面是一个例子:

require(['dojo/_base/array','dojo/dom','dojo/dom-construct','dojo/json'],function(arrayUtil,dom,domConstruct,JSON) {
    // result is a new promise that produces a new value
    var result = original.then(function(response) {
        var userlist = dom.byId("userlist1");

        return arrayUtil.map(response,function(user) {
            domConstruct.create("li",{
                innerHTML: JSON.stringify(user)
            },userlist);

             return {
                 id: user[0],username: user[1],name: user[2]
             };
        });
    });
});
这个then调用返回了一个promise对象,这个promise对象的值将会被回调函数的返回值设定。我们能够看出,新的promise的值和最初的Deferred是不同的。
// chaining to the result promise rather than the original deferred to get our new value

result.then(function(objs) {
    var userlist = dom.byId("userlist2");
    
    arrayUtil.forEach(objs,function(user) {
        domConstruct.create("li",{
            innerHTML: JSON.stringify(user)
        },userlist);
    });
});
promise返回的值都是其回调函数的返回值,如果promise的回调函数并没有返回值,那这个promise的值将会是undefined。如果在你的chaining中出现了undefined,请确保你的promise的回调函数中提供了返回值。如果你不需要考虑chaining,那就不需要担心是否提供了返回值。

此时,我们可以检查最初的Deferred的值并没有改变:

// creating a list to show that the original deferred's value was untouched

original.then(function(response) {
    var userlist = dom.byId("userlist3");
    
    arrayUtil.forEach(response,userlist);
    });
});
正如我们之前所见,chaining是非常强大的,它更强大的地方在于chain中的每个对象是不可变的。

需要注意的是,Deferred实例包括另外一个属性:promise,这是一个只实现了promise接口的对象,但是代表了Deferred的返回值。该promise属性允许你最小化使用者调用你的接口所带来的负面影响,通过避免有意或无意调用resolve或reject方法,但是仍然允许他们获取最初Deferred的值。

dojo/when

dojo/when是Dojo提供的一个强大的函数,它允许你用一致的接口处理promises或常规值。dojo/when函数具有4个参数:一个promise或常规值,一个可选回调函数,一个可选错误处理函数,和一个可选过程(progress)函数。它执行以下两种情况之一:

> 如果第一个参数不是一个promise,且提供了回调函数,第一个参数的值将传递给这个回调函数并立即执行,并返回回调函数的执行结果。如果没有提供回调函数,那第一个参数值将被立即返回。

> 如果第一个参数是一个promise,在promise的then方法中提供了回调函数、错误处理函数和过程(progress)函数,将会返回一个新的promise。设定回调函数用来在promise完成时执行。

下面是一个例子:

function getUserList() {
    return request.get("users-mangled.json",{
        handleAs: "json"
    }).then(function(response) {
        return arrayUtil.map(response,function(user) {
            return {
                id: user[0],name: user[2]
            };
        });
    });
}
假设用户列表不会经常改变,且可以在client端缓存而不是每次函数调用时都去获取它们。在这个情形中,因为dojo/when需要一个常规值或一个promise,getUserList可以改为返回一个promise或者一个用户的数组,然后,我们就可以用dojo/when来处理这个返回值:
require(['dojo/_base/array','dojo/when','dojo/request',when,request,JSON) {
    var getUserList = (function() {
        var users;
        return function() {
            if(!users) {
                return request.get("users-mangled.json",{
                    handleAs: "json"
                }).then(function(response) {
                    // Save the resulting array into the users variable
                    users = arrayUtil.map(response,function(user) {
                        return {
                            id: user[0],name: user[2]
                        };
                    });

                     // Make sure to return users here,for valid chaining
                     return users;
                });
            }
            return users;
        };
    })();
});

when(getUserList(),function(users) {
    // This callback will be run after the request completes
    var userlist = dom.byId("userlist1");
    arrayUtil.forEach(users,userlist);
    });

    when(getUserList(),function(user) {
        // This callback will run right away since it's already in cache
        var userlist = dom.byId("userlist2");
        arrayUtil.forEach(users,userlist);
        });
    });
});
也可以是你负责用来创建用户列表的API,并想为你的开发者提供一个清析的API来从server端或一个数组传递给你一个用户列表。这种情况下,你可能会用如下的一个方法:
function createUserList(node,users){
    var nodeRef = dom.byId(node);

    return when(
        users,function(users){
            arrayUtil.forEach(users,function(user){
                domConstruct.create("li",{
                    innerHTML: JSON.stringify(user)
                },nodeRef);
            });
        },function(error){
            domConstruct.create("li",{
                innerHTML: "Error: " + error
            },nodeRef);
        }
    );
}

var users = request.get("users-mangled.json",{
    handleAs: "json"
}).then(function(response){
    return arrayUtil.map(response,function(user){
        return {
            id: user[0],name: user[2]
        };
    });
});

createUserList("userlist1",users);
createUserList("userlist2",[{ id: 100,username: "username100",name: "User 100" }]);
如上,dojo/when允许开发者用一个接口同时处理同步和异步情形,同时在生产者和消费者范围。

用dojo/promise/all处理promises列表

dojo/promise/all代替了dojo/DeferredList模块,通过结合多个promises结果为一个promise提供了处理多个异步操作机制。有时,你需要并行获取多个源的数据,并想在所有请求都结束时获得一个通知,dojo/promise/all就提供了这样的解决方法。

dojo/promise/all的使用很简单,只要向它的构造函数传递一个对象或Deferreds的数组,返回结果是一个使用在传递参数中相同键的对象,或者一个和传入数组相同顺序的数组,下面是一个说明例子:

require(["dojo/promise/all","dojo/Deferred","dojo/request","dojo/_base/array","dojo/dom-construct","dojo/dom","dojo/json","dojo/domReady!"],function(all,Deferred,arrayUtil,JSON){
    var usersDef = request.get("users.json",{
        handleAs: "json"
    }).then(function(response){
        var users = {};

        arrayUtil.forEach(response,function(user){
            users[user.id] = user;
        });

        return users;
    });

    var statusesDef = request.get("statuses.json",{
        handleAs: "json"
    });
    all([usersDef,statusesDef]).then(function(results){
        var users = results[0],statuses = results[1],statuslist = dom.byId("statuslist");

        if(!results[0] || !results[1]){
            domConstruct.create("li",{
                innerHTML: "An error occurred"
            },statuslist);
            return;
        }
        arrayUtil.forEach(statuses,function(status){
            var user = users[status.userId];
            domConstruct.create("li",{
                id: status.id,innerHTML: user.name + ' said,"' + status.status + '"'
            },statuslist);
        });
    });
});
这里,我们想从server端同时获取用户列表和状态列表,通过注册一个回调函数返回用户ID的hash,将两个Deferreds都传递给dojo/promise/all,并为其注册了一个回调函数,该回调函数检查错误,如果没有错误,就会遍历状态数组,将其和用户匹配起来。无论哪个请求先执行结束,dojo/promise/all都会返回一个Deferreds传入时的顺序的数组。

总结

Dojo的promises接口为开发者提供了两种创建更加强大的应用的机会:由于Deferred函数返回的promises是不可变的避免了一些负面影响,且dojo/when提供了一个跨越基于promise和基于常规值编码的鸿沟。基于这个,dojo/promise/all允许你用一个回调函数处理多个deferreds/promises。

相关文章

我有一个网格,可以根据更大的树结构编辑小块数据.为了更容易...
我即将开始开发一款教育性的视频游戏.我已经决定以一种我可以...
我正在使用带有Grails2.3.9的Dojo1.9.DojoNumberTextBox小部...
1.引言鉴于个人需求的转变,本系列将记录自学arcgisapiforja...
我正在阅读使用dojo’sdeclare进行类创建的语法.描述令人困惑...
我的团队由更多的java人员和JavaScript经验丰富组成.我知道这...