如何依次执行四个异步功能?

时间:2019-07-29 22:59:23

标签: javascript node.js amazon-web-services asynchronous amazon-dynamodb

我正在使用包含四个异步功能的这段代码。 我需要他们严格执行。
我该如何执行它们将按照示例中给出的顺序执行?
我的用例在Lambda中,并且可以访问异步。

    function scanProducts() {
      dynamoClient.scan(productParams, function (err, data) {

      });
    }

    function scanCoupons() {
      dynamoClient.scan(couponsParams, function (err, data) {

      });
    }

    function scanRetailers() {
      dynamoClient.scan(retailerParams, function (err, data) {

      });
    }

    function sendEmail(ses) {
      var email = {
        "Source": "test@gmail.com",
        "Template": "test-template",
        "Destination": {
          "ToAddresses": ["test@gmail.com"]
        },
        "TemplateData": `{}`
      }

      ses.sendTemplatedEmail(email);
    }

3 个答案:

答案 0 :(得分:5)

我会将dynamoClient.scan转换为基于Promise的函数,然后await对其进行每次调用,例如:

const dynamoClientScanProm = (params) => new Promise((resolve, reject) => {
  dynamoClient.scan(params, function (err, data) {
    if (err) reject(err);
    else resolve(data);
  });
});

// ...
// in an async function:

try {
  await dynamoClientScanProm(productParams);
  await dynamoClientScanProm(couponsParams);
  await dynamoClientScanProm(retailerParams);

  // promisify/await this too, if it's asynchronous
  ses.sendTemplatedEmail(email);
} catch(e) {
  // handle errors
}

尚不清楚您是否需要使用调用结果,但是如果您确实需要结果并且不只需要等待Promise解析,则在await处理时将其分配给一个变量,例如

const productResults = await dynamoClientScanProm(productParams);

也就是说,如果dynamoClientScanProm的其他调用未使用结果,则并行运行所有调用(使用Promise.all而不是串行运行更有意义),这样就可以更快地完成整个过程。

答案 1 :(得分:2)

答案:

您可以按照Symbol.iterator使用for await来执行诺言的异步执行。可以将其打包到一个构造函数中,在示例示例中,它被称为Serial(因为我们要按顺序逐个处理promises)

function Serial(promises = []) {
    return {
        promises,
        resolved: [],
        addPromise: function(fn) {
            promises.push(fn);
        },
        resolve: async function(cb = i => i, err = (e) => console.log("trace: Serial.resolve " + e)) {
            try {
                for await (let p of this[Symbol.iterator]()) {}
                return this.resolved.map(cb);
            } catch (e) {
                err(e);
            }
        },
        [Symbol.iterator]: async function*() {
            this.resolved = [];
            for (let promise of this.promises) {
                let p = await promise().catch(e => console.log("trace: Serial[Symbol.iterator] ::" + e));
                this.resolved.push(p);
                yield p;
            }
        }
    }
}

以上是什么?

  • 这是一个名为Serial的构造函数。
  • 它以返回Promises的Function数组作为参数。
  • 函数存储在Serial.promises
  • 它有一个空数组存储在Serial.resolved中-这将存储已解决的承诺请求。
  • 它有两种方法:
    • addPromise:获取一个返回Promise的函数并将其添加到Serial.promises
    • resolve:异步调用自定义Symbol.iterator。该iterator会经历每个单独的承诺,等待其完成,然后将其添加到Serial.resolved中。完成此操作后,它将返回对填充的Serial.resolved数组起作用的map函数。这使您可以简单地调用resolve,然后在响应中提供有关成员处理的回调。如果将诺言返回给该函数,则可以传递一个then函数以得到整个数组。

一个例子:

  promises.resolve((resolved_request) => { 
     //do something with each resolved request 

       return resolved_request;
  }).then((all_resolved_requests) => { 

     // do something with all resolved requests

  });

下面的示例显示了如何将其有效地使用,无论您希望在每个单独的分辨率上发生什么,还是等到一切完成。

请注意,它们将始终保持顺序。这可以通过以下事实证明:第一个计时器的计数最高ms。第二个Promise 将不会开始,直到第一个Promise结束,第三个Promise不会在第二个Promise完成之前开始,等等。

这使我很重要。尽管按顺序序列化您的承诺是有效的,但重要的是要意识到,如果其中的任何一个花费了任何时间,这将延迟您对数据的响应。 Parallel的优点在于,如果一切顺利,所有请求将花费更短的时间来完成。如果应用程序具有多个必需的请求,那么诸如序列化之类的东西非常有用,如果其中一个不可用,或者一个项目依赖于另一个项目(相当常见),则整个过程将会失败。

//helpers 
let log = console.log.bind(console),
promises = Serial(),
    timer = (tag, ms) => () => new Promise(res => { 
  setTimeout(() => {
  res("finished " + tag);
}, ms) });




function Serial(promises = []) {
    return {
        promises,
        resolved: [],
        addPromise: function(fn) {
            promises.push(fn);
        },
        resolve: async function(cb = i => i, err = (e) => console.log("trace: Serial.resolve " + e)) {
            try {
                for await (let p of this[Symbol.iterator]()) {}
                return this.resolved.map(cb);
            } catch (e) {
                err(e);
            }
        },
        [Symbol.iterator]: async function*() {
            this.resolved = [];
            for (let promise of this.promises) {
                let p = await promise().catch(e => console.log("trace: Serial[Symbol.iterator] ::" + e));
                this.resolved.push(p);
                yield p;
            }
        }
    }
}


promises.addPromise(timer(1, 3000));
promises.addPromise(timer(2, 1000));
promises.addPromise(timer(3, 2000));

promises
        .resolve(msg => ( log(msg), msg) )
        .then((complete) => log("everything is complete: " + complete));


它如何工作?

通过使用一个迭代器依次调用每个promise,我们可以确定他们 被依次收到

尽管许多人没有意识到,Symbol.iterator比标准for循环更强大。这有两个主要原因。

第一个原因(在这种情况下适用)是因为它允许异步调用,这会影响所应用对象的状态。

第二个原因是它可用于从同一对象提供两种不同类型的数据。 A.e.您可能有一个要读取以下内容的数组:

let arr = [1,2,3,4];

您可以使用for循环或forEach来获取数据:

arr.forEach(v => console.log(v)); 
// 1, 2, 3, 4

但是如果您调整迭代器:

arr[Symbol.iterator] = function* () {
  yield* this.map(v => v+1);
};

您得到了:

arr.forEach(v => console.log(v));
// 1, 2, 3, 4
for(let v of arr) console.log(v);
// 2, 3, 4, 5

由于许多不同的原因,这很有用,包括为请求加盖时间戳/映射引用等。如果您想了解更多信息,请查看ECMAScript文档:For in and For Of Statements


使用:

可以通过调用返回Promises的函数数组的构造函数来使用它。您还可以使用

将功能承诺添加到对象
new Serial([])
.addPromise(() => fetch(url))

在您使用.resolve方法之前,它不会运行功能承诺。

这意味着您可以在对异步调用执行任何操作之前随意添加Promise。 A.e.这两个是相同的:

使用addPromise:

 let promises = new Serial([() => fetch(url), () => fetch(url2), () => fetch(url3)]); 
 promises.addPromise(() => fetch(url4));
 promises.resolve().then((responses) => responses)

没有addPromise:

 let promises = new Serial([() => fetch(url), () => fetch(url2), () => fetch(url3), () => fetch(url4)])
.resolve().then((responses) => responses)

调整代码:

下面是调整代码以按顺序执行操作的示例。事实是,您实际上并未提供很多入门代码,因此我将您的scan函数替换为先前示例中使用的timer函数。

要使用您的代码来实现此功能,您要做的就是从scan函数中返回一个Promise,它将很好地工作:)

function Serial(promises = []) {
    return {
        promises,
        resolved: [],
        addPromise: function(fn) {
            promises.push(fn);
        },
        resolve: async function(cb = i => i, err = (e) => console.log("trace: Serial.resolve " + e)) {
            try {
                for await (let p of this[Symbol.iterator]()) {}
                return this.resolved.map(cb);
            } catch (e) {
                err(e);
            }
        },
        [Symbol.iterator]: async function*() {
            this.resolved = [];
            for (let promise of this.promises) {
                let p = await promise().catch(e => console.log("trace: Serial[Symbol.iterator] ::" + e));
                this.resolved.push(p);
                yield p;
            }
        }
    }
}


const timer = (tag, ms) => new Promise(res => {
  setTimeout(() => {
    res("finished " + tag);
  }, ms)
});


function scanProducts() {
  return timer("products", 3000);
}

function scanCoupons() {
  return timer("coupons", 1000);
}

async function scanRetailers() {
  return timer("retailers", 2500);
}

function sendEmail(ses) {
  var email = {
    "Source": "test@gmail.com",
    "Template": "test-template",
    "Destination": {
      "ToAddresses": ["test@gmail.com"]
    },
    "TemplateData": `{}`
  }

  ses.sendTemplatedEmail(email);
}

let promises = Serial([scanProducts, scanCoupons, scanRetailers]);

promises.resolve().then(resolutions => console.log(resolutions));


希望这会有所帮助!祝您编码愉快!

答案 2 :(得分:0)

使用异步系列。它尽可能简单地依次执行一系列回调。

<match **>
  @type elasticsearch
  index_name whatever.${key1}
  <buffer tag, key1>
    logstash_prefix ${key1}
  </buffer>
</match>