【问题标题】:What's the best way to loop through ajax calls循环通过ajax调用的最佳方法是什么
【发布时间】:2017-02-20 02:57:50
【问题描述】:

我正在使用 Sharepoint,并且我有一个在网站集中创建列表的应用程序(不是应用程序!)。创建列表不是问题。 我不想创建 15 列并将它们添加到默认视图。我准备了一个包含列表及其字段信息的“配置”。

var ChangeRequestLogListConfig = {
    listName: 'ChangeRequestLog',
    fields: [
        { 'FieldTypeKind': 8, 'Title': 'STC Relevant', 'InternalName': 'STCrelevant', 'StaticName': 'STCrelevant' },
        { 'FieldTypeKind': 3, 'Title': 'Description/Reason', 'InternalName': 'DescriptionReason', 'StaticName': 'DescriptionReason' },
        { 'FieldTypeKind': 6, 'Title': 'Source of Change', 'InternalName': 'SourceOfChange', 'StaticName': 'SourceOfChange', 'Choices': { 'results': ['customer', 'internal'] } },
        //12 more objects like the above
    ]
}

问题来了:其余资源 .../ViewFields/AddViewField.../fields(使用 http 帖子创建新资源)仅支持一个参数,这意味着我必须进行 2x15 次 ajax 调用。

执行所有这 30 个操作然后执行最终回调的正确方法是什么?当然我知道如何遍历字段数组,但我根本不知道如何以正确的方式构建最终回调。

更新/跟进

在一些答案的帮助下,我设法构建了以下代码:

var spExecutor = new SP.RequestExecutor(_spPageContextInfo.siteAbsoluteUrl);
var requestUrl = _spPageContextInfo.siteAbsoluteUrl + "/_api/SP.AppContextSite(@target)/web/Lists/getbytitle('" + listConfig.listName + "')/fields?@target='" + hostWebUrl + "'";

//map field configs to promises
var promises = listConfig.fields.map(fieldConfig => {
    return spExecutor.executeAsync({
        url: requestUrl,
        method: "POST",
        body: JSON.stringify(fieldConfig),
        headers: {
            "accept": "application/json;odata=verbose",
            "content-type": "application/json; odata=verbose"
        },
        error: err => console.log(err)
    });
});

//Wait for all calls to be done
$.when.apply($, promises).then(callbackFn);

您可能已经看到,我使用的不是$.ajax,而是SP.RequestExecutor。 问题:这不会以与 jQuery 的 ajax 相同的方式返回 Promise。这会导致时间问题(--> 一次调用 15 个,浏览器大多只支持 4 个并行调用)。上面的代码有效,如果我设置断点并在调用之间等待 1-2 秒,它会按预期创建所有字段。

我的后续问题:如何等待第一个调用完成以初始化第二个,然后是第三个,依此类推? 我不确定我是否以正确的方式使用承诺。

【问题讨论】:

  • 你不能把它捆绑成更少的请求吗?提交任务列表供服务器完成,然后响应一个大响应?
  • 您可以将每个调用包装在一个 promise 中,然后调用 Promise.all([p1, p2, ...])Promise.all 的结果将是一个数组,每个项目都是每个 ajax 调用的结果。
  • @EmilS.Jørgensen 不,正如所说的,端点当时只允许一个。 @NickSalloum 我该怎么做? var responses = Promise.all(...)? p1,p2 会是什么?

标签: javascript jquery ajax promise deferred


【解决方案1】:

使用.map将每个字段转换为Promise

var promises = ChangeRequestLogListConfig.fields.map(
    item => $.ajax( ... )
);

然后$.when 等待它们全部完成:

$.when.apply($, promises).then( ... );

请注意,这将在您的浏览器允许的情况下并行启动尽可能多的 AJAX 请求 - 通常是四个。

编辑,因为您说过您实际上希望 AJAX 查询连续运行(以避免服务器 409 错误),我的 addFieldsToList 函数版本如下所示:

function addFieldsToList(listConfig) {

    return listConfig.fields.reduce(function(promise, field) {
        return promise.then(executeRequest(field, listConfig.listName));
    }, Promise.resolve(null));

}

避免传递回调,因为此函数的返回值本身将是您可以链接的Promise

addFieldsToList(myConfig).then(callback);

【讨论】:

  • apply中$的作用是什么?
  • @Sandrooco 它使 jQuery 对象成为$.when 函数中this 的值,就像直接使用$.when(p0, p1, ...) 调用一样
  • 这很好用,但唯一的问题是:我刚刚看到我必须使用 SP.RequestExecutor 从 appweb 转到 hostweb...它不起作用,因为我认为它不起作用t 支持承诺。如何做出“自定义”承诺?
  • 这是我所能得到的。尽管这篇文章没有记录在多个调用中使用 deferreds。看看更新的问题,可能会更清楚一点。非常感谢!
【解决方案2】:

最后我这样做了:

//Promise returning request
function executeRequest(fieldConfig, listName) {
    return function () {
        return new Promise(function (resolve, reject) {
            var requestUrl = _spPageContextInfo.siteAbsoluteUrl + "/_api/SP.AppContextSite(@target)/web/Lists/getbytitle('" + listName + "')/fields?@target='" + riskapp.utils.getSpHostUrl() + "'";
            var spExecutor = new SP.RequestExecutor(_spPageContextInfo.siteAbsoluteUrl);
            spExecutor.executeAsync({
                url: requestUrl,
                method: "POST",
                body: JSON.stringify(fieldConfig),
                headers: {
                    "accept": "application/json;odata=verbose",
                    "content-type": "application/json; odata=verbose"
                },
                success: resolve,
                error: reject
            });
        });
    };
}

//Add fields to list according to config
function addFieldsToList(listConfig, callback) {

    //Prepare empty/resolved promise to iterate later
    var promise = Promise.resolve(null);

    //Loop through every field 
    $.each(listConfig.fields, function (i, field) {
        promise = promise.then(executeRequest(listConfig[field], listConfig.listName));
    });

    //execute callback when all fields are created
    promise.then(callback);
}

这会按顺序执行所有调用,而不是同时执行。

【讨论】:

  • 是的,这是另一种方法,但它们将被严格序列化,不会那么快。在编写了executeRequest 之后,您仍然可以使用.map$.whenPromise.all() 来处理Promise 数组。注意:new Promise() 仅适用于 ES6 浏览器。使用 jQuery,您可以改用 $.Deferred(),或者为旧版浏览器使用诸如 Bluebird 之类的 Promise shim。
  • 另一种进行序列化调用的巧妙方法是使用return array.reduce(function(p, field) { return p.then(...) }, Promise.resolve())
  • 另外,不要将回调传递给addFieldsToList,您应该只使用return promise,并将其称为:addFieldsToList(...).then(callback) - 将过程与后续操作分开。
  • 非常感谢@Alnitak。对我来说很重要:必须对调用进行序列化,因为我每次都访问同一个 SP 列表,如果我同时去,它会返回 409。您将如何在我的解决方案中使用延迟?
  • 如果你有标准的 Promise 工作(需要 ES6 或 shim),那么没有必要改用 $.Deferred - ES6 风格的 Promise 更好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-05
  • 1970-01-01
  • 2019-11-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多