【问题标题】:Synchronous Javascript Function with Promise doesnt work properly带有 Promise 的同步 Javascript 函数无法正常工作
【发布时间】:2018-06-13 13:55:20
【问题描述】:

我希望同步处理以下函数,但不知何故无法正常工作。

function upload_to_aws(data) {
  return new Promise(function(resolve, reject) {
    loan_application_id = $('#loan_application_id').val();

    var s3BucketName = data.bucket_name;
    var s3RegionName = data.region;
    AWS.config.update({accessKeyId: data.key, secretAccessKey: data.secret_key, region: s3RegionName});
    var s3 = new AWS.S3({params: {Bucket: s3BucketName, Region: s3RegionName}});

    aws_url= []
    $('.attached_image').each(function() {
      if($(this).attr('src') != "/assets/upload_bg.png" && $(this).attr('src') != '' ) {
        var timestamp = (new Date()).getTime();
        var randomInteger = Math.floor((Math.random() * 1000000) + 1);
        filename = 'self_evaluation_images/'+ loan_application_id + '_self_eval_ic_' + timestamp  + '.png';
        var u = $(this).attr('src').split(',')[1],
          binary = atob(u),
          array = [];

        for (var i = 0; i < binary.length; i++) {
            array.push(binary.charCodeAt(i));
        }

        var typedArray = new Uint8Array(array);
        s3_upload(s3, filename, typedArray).then(function(url_aws) {
          aws_url.push(url_aws);
          console.log(aws_url)
          console.log(aws_url.length)
        })
      }
    })
    resolve(aws_url);
  })
}

function s3_upload(s3, filename, typedArray) {
  return new Promise(function(resolve, reject) {
    s3.putObject({Key: filename, ContentType: 'image/png', Body: typedArray.buffer, ContentEncoding: 'base64', ACL: 'public-read'},
    function(err, data) {
        if (data !== null) {
          url_aws = s3.endpoint.href + filename;
          resolve(url_aws)
        }
        else {
          reject(err);
        }
    });
  })
}

当调用此函数时,它会调用 upload_to_aws 函数,我希望在它返回 aws_uploaded url 数组之前在该函数中执行所有操作。

$.when(upload_to_aws(data.data)).then(function(aws_uploaded_url) {
   console.log(aws_uploaded_url);
})

但目前基本上发生的情况是,在将图像上传到 s3 期间,即使在图像上传到 s3 之前,它也会被称为 resolve(aws_url),因此这会将 console.log(aws_uploaded_url) 打印为空数组 [],因为该函数尚未完全执行。

还有其他方法可以在javascript中处理回调和同步函数吗?

【问题讨论】:

  • 您应该将resolve(aws_url); 放在s3_upload 承诺的then 函数中

标签: javascript jquery asynchronous es6-promise


【解决方案1】:

主要问题是代码不会等待s3_update 返回的承诺得到解决,并在使用该数组解析upload_to_aws 返回的承诺之前填充aws_url 数组(它仍然是空的)。

到目前为止,这是How do I return the response from an asynchronous call? 中解决的常见问题,但用s3.putObject 方法代替了AJAX 调用。

您还希望等待多个(零个或多个承诺),因为请求的数量由数据决定。等待多个 promise 完成涉及到 Promise.all 的使用。

Promise.all 调用的已实现值是作为参数提供的承诺的已实现值的数组,按提供的参数顺序排列。换句话说,完成的值是帖子中使用的aws_url数组。

尝试一种(未经测试的)方法,稍作修改以声明所有变量并简化s3_upload 可能是:

function upload_to_aws(data) {
    var loan_application_id = $('#loan_application_id').val();
    var s3BucketName = data.bucket_name;
    var s3RegionName = data.region;
    AWS.config.update({accessKeyId: data.key, secretAccessKey: data.secret_key, region: s3RegionName});
    var s3 = new AWS.S3({params: {Bucket: s3BucketName, Region: s3RegionName}});

    var urlPromises = [];
    $('.attached_image').each(function() {
      if($(this).attr('src') != "/assets/upload_bg.png" && $(this).attr('src') != '' ) {
        var timestamp = (new Date()).getTime();
        var randomInteger = Math.floor((Math.random() * 1000000) + 1);
        var filename = 'self_evaluation_images/'+ loan_application_id + '_self_eval_ic_' + timestamp  + '.png';
        var u = $(this).attr('src').split(',')[1];
        var binary = atob(u);
        var array = [];
        for (var i = 0; i < binary.length; i++) {
            array.push(binary.charCodeAt(i));
        }
        var typedArray = new Uint8Array(array);
        urlPromises.push( s3_upload(s3, filename, typedArray))
      }
    });
    return Promise.all( urlPromises);
}

function s3_upload(s3, filename, typedArray) { // promisify a call back
  return new Promise(function(resolve, reject) {
    s3.putObject(
        {Key: filename, ContentType: 'image/png', Body: typedArray.buffer, ContentEncoding: 'base64', ACL: 'public-read'},
        function(err, data) { err ? reject(err) : resolve( data);}
    );
  });
}

(如果任何声明的变量应该是全局的,则删除它们前面的var)。

调用upload_to_aws返回的promise应该用一个由零个或多个上传的url组成的数组来实现:

$.when(upload_to_aws(data.data)).then(function(aws_uploaded_urls {
    console.log(aws_uploaded_urls);
})


JQuery 兼容性(更新)

在版本 3 之前,jQuery 没有实现符合 Aplus promise specificaion 或更高版本的 ECMAScript 版本 6 标准的承诺。旧版本的 JQuery 根本无法将 ES6 Promise 识别为 Promise,并且无法等待它们被解决。

检查您是否在使用原生 Promises 的代码中使用 JQuery 3 或更高版本。如果您需要支持 IE 浏览器,您还需要为支持 Promise.all 的 ES6 样式 promise 添加 polyfill。

如果您需要支持不再是 supported in JQuery 3 的浏览器,请考虑完全删除 Promise 的使用,例如,围绕 Deferred 对象的使用重构代码(超出此答案的范围)。这也将消除在缺乏原生 Promise 支持的旧浏览器中对 polyfill 的需求。

如果.when 方法在使用 ES6 Promise 时出现问题,请考虑使用纯 JavaScript 调用代码:

upload_to_aws(data.data)
.then(function(aws_uploaded_urls) {
    console.log(aws_uploaded_urls);
})
.catch( function( err) {
   console.log( "upload_to_aws failed: ", err);
}

【讨论】:

    【解决方案2】:

    因为每次迭代都在异步运行,所以您需要改用Promise.all,将每次迭代映射到Promise,并在所有迭代的promise完成后最终解析upload_to_aws返回的promise:

    function upload_to_aws(data) {
        loan_application_id = $('#loan_application_id').val();
    
        var s3BucketName = data.bucket_name;
        var s3RegionName = data.region;
        AWS.config.update({accessKeyId: data.key, secretAccessKey: data.secret_key, region: s3RegionName});
        var s3 = new AWS.S3({params: {Bucket: s3BucketName, Region: s3RegionName}});
    
        return Promise.all(
          [...document.querySelectorAll('.attached_image')]
          .map(image => new Promise((resolve, reject) => {
            const { src } = image;
            if(src != "/assets/upload_bg.png" && src != '' ) {
              var timestamp = (new Date()).getTime();
              var randomInteger = Math.floor((Math.random() * 1000000) + 1);
              filename = 'self_evaluation_images/'+ loan_application_id + '_self_eval_ic_' + timestamp  + '.png';
              var u = src.split(',')[1],
                  binary = atob(u),
                  array = [];
    
              for (var i = 0; i < binary.length; i++) {
                array.push(binary.charCodeAt(i));
              }
    
              var typedArray = new Uint8Array(array);
              s3_upload(s3, filename, typedArray).then(resolve, reject);
            }
          }))
      );
    }
    

    【讨论】:

    • 你可能更喜欢最后一行 s3_upload(s3, filename, typedArray).then(resolve, reject); 来传递错误
    • @CertainPerformance,这看起来不错,但它仍然打印Promise &lt;pending&gt;,并且解析部分在函数完成执行此部分之前执行。 $.when(upload_to_aws(data.data)).then(function(aws_uploaded_url) { console.log(aws_uploaded_url); })
    • @KingsleySimon 你确定你完全复制了代码吗?在我的函数开始处没有明确的Promise 被构造。您可能会考虑单独使用原生 Promises,而不是将 jQuery 加入其中 - 例如 upload_to_aws(data.data).then(aws_uploaded_url =&gt; console.log(aws_uploaded_url))
    • @KingsleySimon 不幸的是,我无法重现该问题。是aws_uploaded_url 本身看起来是Promise,还是Promises 的数组?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-11
    • 1970-01-01
    • 2020-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多