具有Promise的同步Javascript函数无法正常工作

时间:2018-06-12 04:37:24

标签: javascript jquery asynchronous es6-promise

我有以下功能,我更愿意同步处理,但不知怎的,它无法正常工作。

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中处理回调和同步函数吗?

2 个答案:

答案 0 :(得分:1)

主要问题是代码不会等待s3_update返回的promise被解决并填充aws_url数组,然后再解析upload_to_aws与该数组返回的promise(它还是空的。)

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

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

Promise.all调用的实现值是作为参数提供的promise的已满足值的数组,按提供的参数顺序排列。换句话说,实现的值是帖子中使用的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返回的承诺应使用零个或多个上传网址的数组来实现:

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

<小时/> JQuery兼容性(更新)

在版本3之前,jQuery没有实现符合Aplus promise specificaion或后来的ECMAScript版本6标准的promise。较旧版本的JQuery无法将ES6承诺视为承诺而无法等待它们得到解决。

检查您使用的是使用JQuery 3或更高版本的代码,该代码使用本机Promise。如果您需要支持IE浏览器,您还需要包含支持Promise.all的ES6样式承诺的polyfill。

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

如果.when方法与ES6保证使用一起出现问题,请考虑使用纯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);
}

答案 1 :(得分:0)

因为每次迭代都在运行异步,所以需要使用Promise.all,将每次迭代映射到Promise,最后解析upload_to_aws一旦所有迭代的承诺返回的承诺完成:

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);
        }
      }))
  );
}