顺序的、依赖的延迟调用

Sequential, Dependent Deferred calls

本文关键字:调用 延迟 顺序 依赖      更新时间:2023-09-26

我有两个函数getStudentById(studententid)getBookTitleById(bookId)其中数据是通过ajax调用检索的(这些函数是单独的)

我的目标,使用deferred:

  1. 获取学生对象,
  2. 然后根据Student对象的favoriteBookId属性获取书名,
  3. 然后调用方法showMessage(student, bookTitle)导致"John likes Book 222"

这个例子是简化的。本质上,目标是连续进行一定数量的ajax调用,其中下一个调用取决于前一个调用的结果,最后提供对所有解析

的访问。代码:

var getStudentById = function(studentId){
  return $.Deferred(function(def){
      def.resolve({name:"John",favoriteBookId:222});  //assume ajax gets Student obj
    }).promise();  
}
var getBookTitleById = function(bookId){
  return $.Deferred(function(def){
      def.resolve("Book " + bookId);  //assume ajax call gets title
    }).promise();   
}
var showMessage = function(student, bookTitle){
   alert(student.name + " likes " + bookTitle)
} 

我见过许多遵循以下模式的解决方案

deferred_a.then(b).then(c).then(y).then(z).done(r)

可以翻译成:

getStudentById(5)    //resolves with student object
.then(function(student){
    return getBookTitleById(student.favoriteBookId)}  
)   
.done(function(bookTitle){
    alert(bookTitle);  //obviously  this works
    // how can I get reference to the student object here? so i can say:
    // showMessage (student, bookTitle)
});

,但我需要调用方法showMessage(学生,bookTitle),其中参数是每个ajax调用链的结果

我找不到一个(优雅的)示例,其中顺序延迟调用的所有结果都可以在链的末尾访问,因为如果done函数获得LAST 的结果,那么

我最好的解决方案是将第二个Deferred封装在工厂类型的方法中,但这不是最好的解决方案,对吗?我是否错过了延迟使用中应该简化的内容?

getStudentById(5)    
.then(function(student){
        return $.Deferred(function(def){
            getBookTitleById(student.favoriteBookId)  
            .done(function(bookTitle){def.resolve({student:student, bookTitle:bookTitle})})
        }).promise();   
    })  
.done(function(studentAndBookTitle){
    alert(studentAndBookTitle.student.name + " likes " + studentAndBookTitle.bookTitle);
});

Genob,你的"最佳解决方案"是完全可行和可接受的。它只需要整理:

getStudentById(5).then(function(student) {
    return getBookTitleById(student.favoriteBookId).then(function(bookTitle) {
        return {
            student: student,
            bookTitle: bookTitle
        });
}).done(function(superObj) {
    alert(superObj.student.name + " likes " + superObj.bookTitle);
});

您还可以考虑同一方法的一个温和的变体,这是可能的,因为student已经是一个对象,允许扩展它,而不是创建一个超级对象。

getStudentById(5).then(function(student) {
    return getBookTitleById(student.favoriteBookId).then(function(bookTitle) {
        return $.extend(student, {
            favoriteBookTitle: bookTitle
        });
    });
}).done(function(extendedStudent) {
    alert(extendedStudent.name + " likes " + extendedStudent.favoriteBookTitle);
});

这种方法(两种变体)可以概括为"在一个累加器对象中沿着承诺链传递数据",或者更简单地说是"承诺数据累加器"模式(我的术语——你在其他任何地方都找不到)。

无论如何实现,保持"消费者"方法(终端.done())不受嵌套/闭包的限制是好的。

将承诺链接在一起的另一种方法是让一个then处理程序返回另一个承诺。这样做的好处是,你可以利用闭包在第二个承诺的解析中引用第一个承诺解析中的变量。

所以你的例子看起来像:

getStudentById(5)
.then(function(student) {
    return getBookTitleById(student.favoriteBookId)
    .then(function (bookTitle) {
        doSomethingWithStudentAndBookTitle(student, bookTitle)
    });
});
function doSomethingWithStudentAndBookTitle(student, bookTitle) {
    alert(student.name + " likes " + bookTitle);
}

编辑:这个解决方案有点类似于你的上一个片段;但

  1. getBookTitleById已经返回了一个承诺,你不需要把它包装在一个deferred中,然后从它那里获得承诺。

  2. 你已经使用闭包将这两个属性包装成一个对象;没有理由(至少我没有看到)你需要把这些属性包装起来,然后在另一个地方对它们做一些事情,而不是在那里对它们做一些事情。

编辑2:创建了一个函数,将数据消费与数据提供分离。