Синхронная функция JavaScript с Promise не работает должным образом - PullRequest
0 голосов
/ 12 июня 2018

У меня есть следующие функции, которые я предпочел бы обрабатывать синхронно, но почему-то это не работает должным образом.

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 это называется resolve(aws_url) даже до того, как изображения загружаются на s3, поэтому печатается console.log(aws_uploaded_url) какпустой массив [], потому что функция не выполнена полностью.

Есть ли другой способ обработки обратных вызовов и синхронных функций в javascript?

Ответы [ 2 ]

0 голосов
/ 12 июня 2018

Основная проблема заключается в том, что код не ожидает выполнения обещаний, возвращаемых s3_update, и заполняет массив aws_url, прежде чем разрешить обещание, возвращенное upload_to_aws, с этим массивом (он все еще пуст).

До сих пор это часто задаваемый вопрос, адресованный в Как я могу вернуть ответ от асинхронного вызова? , но заменяя метод s3.putObject для вызова AJAX.

Вы также хотите подождать несколько (ноль или более обещаний), поскольку количество запросов определяется данными.Ожидание завершения нескольких обещаний включает использование 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, должно быть выполнено с нулевым массивомили более загруженных URL:

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


Совместимость с JQuery (обновление)

До версии 3 jQuery не выполнял обещания, соответствующие Спецификация обещаний Aplus или более поздняя версия ECMAScript версии 6.Более старые версии JQuery способны вообще не распознавать обещания ES6 как обещания и не могут дождаться их выполнения.

Убедитесь, что вы используете JQuery 3 или более позднюю версию с кодом, который использует собственные обещания.Если вам нужна поддержка браузеров IE, вам также необходимо включить обещания в стиле polyfill для ES6, поддерживающие Promise.all.

Если вам требуется поддержка браузеров, которые больше не поддерживаются в JQuery 3 рассмотреть возможность полного удаления использования Promise и, скажем, рефакторинга кода вокруг использования объектов Deferred (за рамками этого ответа).Это также устранит необходимость в полизаполнении в старых браузерах, в которых отсутствует встроенная поддержка Promise.

Если метод .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);
}
0 голосов
/ 12 июня 2018

Поскольку каждая итерация выполняет что-то асинхронное, вам нужно вместо этого использовать 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);
        }
      }))
  );
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...