Dojo Deferreds and Promises

来源:互联网 发布:皇室战争卡牌数据 编辑:程序博客网 时间:2024/06/05 13:25

原文: 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 valueresult.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 untouchedoriginal.then(function(response) {    var userlist = dom.byId("userlist3");        arrayUtil.forEach(response, function(user) {        domConstruct.create("li", {            innerHTML: JSON.stringify(user)        }, 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],                username: user[1],                name: user[2]            };        });    });}
假设用户列表不会经常改变,且可以在client端缓存而不是每次函数调用时都去获取它们。在这个情形中,因为dojo/when需要一个常规值或一个promise,getUserList可以改为返回一个promise或者一个用户的数组,然后,我们就可以用dojo/when来处理这个返回值:

require(['dojo/_base/array', 'dojo/when', 'dojo/request', 'dojo/dom', 'dojo/dom-construct', 'dojo/json'], function(arrayUtil, when, request, dom, domConstruct, 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],                            username: user[1],                            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, function(user) {        domConstruct.create("li", {            innerHTML: JSON.stringify(user)        }, 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, function(user) {            domConstruct.create("li", {                innerHTML: JSON.stringify(user)            }, 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],            username: user[1],            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, request, arrayUtil, domConstruct, dom, 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。

0 0