NodeJS 在数组(或对象)上迭代异步的最聪明/最干净的方法是什么?

wfsdck30  于 2023-02-08  发布在  Node.js
关注(0)|答案(6)|浏览(170)

我就是这么做的:

function processArray(array, index, callback) {
    processItem(array[index], function(){
        if(++index === array.length) {
            callback();
            return;
        }
        processArray(array, index, callback);
    });
};

function processItem(item, callback) {
    // do some ajax (browser) or request (node) stuff here

    // when done
    callback();
}

var arr = ["url1", "url2", "url3"];

processArray(arr, 0, function(){
    console.log("done");
});

它有什么好处吗?如何避免那些意大利面条般的代码?

apeeds0o

apeeds0o1#

检查async库,它是为控制流(异步填充)而设计的,它有很多用于数组填充的方法:each,filter,map.查看github上的文档.下面是你可能需要的:

each(arr、迭代器、回调)

将迭代器函数并行应用于数组中的每一项。使用列表中的项和完成时的回调调用迭代器。如果迭代器将错误传递给此回调,则立即使用该错误调用each函数的主回调。

每个序列(arr、迭代器、回调)

each相同,只是迭代器被应用于数组中的每一项,下一个迭代器只在当前迭代器完成处理后才被调用,这意味着迭代器函数将按顺序完成。

dw1jzc5e

dw1jzc5e2#

正如在一些答案中指出的,你可以使用“异步”库。但是有时候你只是不想在你的代码中引入新的依赖。下面是另一种方法,你可以循环并等待一些异步函数的完成。

var items = ["one", "two", "three"];

// This is your async function, which may perform call to your database or
// whatever...
function someAsyncFunc(arg, cb) {
    setTimeout(function () {
        cb(arg.toUpperCase());
    }, 3000);
}

// cb will be called when each item from arr has been processed and all
// results are available.
function eachAsync(arr, func, cb) {
    var doneCounter = 0,
        results = [];
    arr.forEach(function (item) {
        func(item, function (res) {
            doneCounter += 1;
            results.push(res);
            if (doneCounter === arr.length) {
                cb(results);
            }
        });
    });
}

eachAsync(items, someAsyncFunc, console.log);

现在,运行node iterasync.js将等待大约3秒钟,然后打印[ 'ONE', 'TWO', 'THREE' ],这是一个简单的示例,但可以扩展以处理许多情况。

zphenhs4

zphenhs43#

正如正确指出的那样,您必须使用setTimeout,例如:

each_async = function(ary, fn) {
    var i = 0;
    -function() {
        fn(ary[i]);
        if (++i < ary.length)
            setTimeout(arguments.callee, 0)
    }()
}

each_async([1,2,3,4], function(p) { console.log(p) })
wr98u20j

wr98u20j4#

处理数组(或任何其他可迭代对象)异步迭代的最简单方法是使用await操作符(仅在异步函数中)和for of循环。

(async function() {
 for(let value of [ 0, 1 ]) {
  value += await(Promise.resolve(1))
  console.log(value)
 }
})()

你可以使用一个库来转换你可能需要的任何函数,这些函数接受回调来返回承诺。

bq3bfh9z

bq3bfh9z5#

在现代JavaScript中,有一些有趣的方法可以将Array扩展为可异步转换的对象。
这里我想演示一个全新类型AsyncArray的框架,它通过继承Array类型的优点来扩展Array类型,使其成为一个异步可迭代数组。
这只在现代引擎中可用。下面的代码使用了最新的噱头,如私有示例字段和for await...of
如果你不熟悉他们,那么我建议你提前看看上面链接的主题。

class AsyncArray extends Array {
  #INDEX;
  constructor(...ps){
    super(...ps);
    if (this.some(p => p.constructor !== Promise)) {
      throw "All AsyncArray items must be a Promise";
    }
  }
  [Symbol.asyncIterator]() {
    this.#INDEX = 0;
    return this;
  };
  next() {
    return this.#INDEX < this.length ? this[this.#INDEX++].then(v => ({value: v, done: false}))
                                     : Promise.resolve({done: true});
  };
};

所以一个异步迭代数组必须包含承诺。只有这样它才能返回一个迭代器对象,每个next()调用返回一个承诺,最终resolve到一个像{value : "whatever", done: false}{done: true}这样的对象。所以基本上这里返回的所有东西都是一个承诺。await抽象解包其中的值,并把它给我们。
正如我之前提到的,这个AsyncArray类型是从Array扩展而来的,它允许我们使用我们熟悉的Array方法,这将简化我们的工作。
让我们看看会发生什么

class AsyncArray extends Array {
  #INDEX;
  constructor(...ps){
    super(...ps);
    if (this.some(p => p.constructor !== Promise)) {
      throw "All AsyncArray items must be a Promise";
    }
  }
  [Symbol.asyncIterator]() {
    this.#INDEX = 0;
    return this;
  };
  next() {
    return this.#INDEX < this.length ? this[this.#INDEX++].then(v => ({value: v, done: false}))
                                     : Promise.resolve({done: true});
  };
};

var aa = AsyncArray.from({length:10}, (_,i) => new Promise(resolve => setTimeout(resolve,i*1000,[i,~~(Math.random()*100)])));

async function getAsycRandoms(){
  for await (let random of aa){
    console.log(`The Promise at index # ${random[0]} gets resolved with a random value of ${random[1]}`);
  };
};

getAsycRandoms();
8ulbf1ek

8ulbf1ek6#

对于现代的Node.js:

要真正异步地迭代集合,你可以试试我的tiny包,它没有依赖关系,与ESM和CJS模块兼容,使用.d.ts类型,检查代码,它真的很小。
https://www.npmjs.com/package/array-to-async-iterable

您可以像这样使用它:

for await(const el of new AsyncTimeIterator(arrayOfObjects)){
  ...
}

由于JavaScript引擎的微任务和宏任务特性,不能只使用for await of循环。简单地说,使用以下代码,您将无法获得新的HTTP请求,也无法让其他定时器的回调执行:

for await(const el of array){
  ...
}

你强迫V8或其他引擎执行所有的微任务(循环迭代),当循环完成时,你将解除阻塞事件循环,并准备好接收HTTP连接,所以这段代码完全无用。

相关问题