Promise chain continues before inner promise is re

2019-01-15 22:16发布

问题:

Similar question to Promise resolve before inner promise resolved but I can't get it to work nontheless.

Every time I think I understand promises, I prove myself wrong!

I have functions that are written like this

function getFileBinaryData () {
    var promise = new RSVP.Promise(function(resolve, reject){
        var executorBody = {
            url: rootSite + sourceRelativeUrl + "/_api/web/GetFileByServerRelativeUrl('" + fileUrl + "')/$value",
            method: "GET",
            binaryStringResponseBody: true,
            success: function (fileData) {
                resolve(fileData.body);
            },
            error: function  (argument) {

                alert("could not get file binary body")
            }            
        }
        sourceExecutor.executeAsync(executorBody);
    });
    return promise;
}

function copyFileAction (fileBinaryData) {
    var promise = new RSVP.Promise(function(resolve, reject){
        var executorBody = {
            url: rootSite + targetWebRelativeUrl + "/_api/web/GetFolderByServerRelativeUrl('" + targetList + "')/Files/Add(url='" + fileName + "." + fileExt + "', overwrite=true)",
            method: "POST",
            headers: {
                "Accept": "application/json; odata=verbose"
            },
            contentType: "application/json;odata=verbose",
            binaryStringRequestBody: true,
            body: fileBinaryData,
            success: function (copyFileData) {
                resolve();
            },
            error: function  (sender, args) {

            }
        }
        targetExecutor.executeAsync(executorBody);    
    });
    return promise;
}

that I try to chain like this

$.getScript("/_layouts/15/SP.RequestExecutor.js")
            .then(patchRequestExecutor)
            .then(function(){
                sourceExecutor = new SP.RequestExecutor(sourceFullUrl);
                targetExecutor = new SP.RequestExecutor(targetList);
            })
            .then(getFileInformation)
            .then(getFileBinaryData)
            .then(copyFileAction)
            .then(getTargetListItem)
            .then(updateTargetListItem)
            .catch(function (sender, args) {

            });

or like this

$.getScript("/_layouts/15/SP.RequestExecutor.js")
            .then(patchRequestExecutor)
            .then(function(){
                sourceExecutor = new SP.RequestExecutor(sourceFullUrl);
                targetExecutor = new SP.RequestExecutor(targetList);
            })
            .then(function(){
                return getFileInformation();
            })
            .then(function(){
                return getFileBinaryData();
            })
            .then(function(binaryData){
                return copyFileAction(binaryData)
            })
            .then(function(){
                return getTargetListItem();
            })
            .then(function(listItem){
                return updateTargetListItem(listItem);
            });

The problem though is that even if I return new promises, the execution continues down the chain before any of the inner promises are resolved. How come? Shouldn't it wait until the asynchronous request has succeeded and resolve() is called in the success callback?

回答1:

You've done nothing wrong here. This is jQuery's fault, as so often1.

The problem is that jQuery is not Promises/A+ compatible (until v 3.0), and fails to adopt promises/thenable from other implementations than its own. So when your callbacks do return RSVP promises, jQuery just treats them as values to fulfill with, instead of waiting for them.
You could cast all of your promises to a jQuery deferred and it would work, but you really don't want that. To get the standard Promise behaviour (as offered by RSVP) to work, you will need to avoid jQuery's buggy then. This can easily be done:

RSVP.Promise.resolve($.getScript("/_layouts/15/SP.RequestExecutor.js"))
//   ^^^^^^^^^^^^^^^
    .then(patchRequestExecutor)
    .then(function(){
        sourceExecutor = new SP.RequestExecutor(sourceFullUrl);
        targetExecutor = new SP.RequestExecutor(targetList);
    })
    .then(getFileInformation)
    .then(getFileBinaryData)
    .then(copyFileAction)
    …