簡體   English   中英

Node.js 原生 Promise.all 是並行處理還是順序處理?

[英]Is Node.js native Promise.all processing in parallel or sequentially?

我想澄清這一點,因為文檔對此不太清楚;

Q1: Promise.all(iterable)是按順序還是並行處理所有的promise? 或者,更具體地說,它是否相當於運行鏈式承諾,如

p1.then(p2).then(p3).then(p4).then(p5)....

或者是其他類型的算法,其中所有p1p2p3p4p5等都被同時(並行)調用,並且一旦所有解決(或一個拒絕)就返回結果?

Q2:如果Promise.all是並行運行的,有沒有方便的方式順序運行一個可迭代對象?

注意:我不想使用 Q 或 Bluebird,而是所有原生 ES6 規范。

Promise.all(iterable)執行所有承諾?

不,承諾不能“被執行”。 它們在被創建時開始他們的任務——它們只代表結果——並且甚至在將它們傳遞給Promise.all之前並行執行所有事情。

Promise.all等待多個承諾。 它不關心它們以什么順序解析,或者計算是否並行運行。

有沒有一種方便的方法來按順序運行可迭代對象?

如果你已經有你的承諾,除了Promise.all([p1, p2, p3, …]) (它沒有序列的概念),你不能做太多Promise.all([p1, p2, p3, …]) 但是,如果您確實有一個可迭代的異步函數,您確實可以按順序運行它們。 基本上你需要從

[fn1, fn2, fn3, …]

fn1().then(fn2).then(fn3).then(…)

解決方案是使用Array::reduce

iterable.reduce((p, fn) => p.then(fn), Promise.resolve())

在平行下

await Promise.all(items.map(async (item) => { 
  await fetchItem(item) 
}))

優點:更快。 即使稍后失敗,所有迭代也將開始。

按順序

for (let i = 0; i < items.length; i++) {
  await fetchItem(items[i])
}

優點:循環中的變量可以被每次迭代共享。 表現得像普通的命令式同步代碼。

NodeJS 不會並行運行 promise,而是並發運行它們,因為它是單線程事件循環架構。 通過創建一個新的子進程來利用多核 CPU,可以並行運行事物。

並行與並發

事實上, Promise.all所做的是,將 promises 函數堆疊在適當的隊列中(參見事件循環架構)並發運行它們(調用 P1,P2,...)然后等待每個結果,然后使用所有承諾的結果。 Promise.all 將在第一個失敗的承諾中失敗,除非你自己管理了拒絕。

並行和並發有一個很大的區別,第一個會在不同的進程中同時運行不同的計算,它們會按照那里的節奏進行,而另一個會一個接一個地執行不同的計算,而無需等待前一個計算同時完成和進行而不相互依賴。

最后,回答你的問題, Promise.all不會並行或順序執行,而是並發執行。

Bergis 的回答使我使用 Array.reduce 走上了正確的軌道。

然而,為了真正讓函數返回我的承諾以一個接一個地執行,我不得不添加更多的嵌套。

我真正的用例是一組文件,由於下游限制,我需要一個接一個地傳輸這些文件...

這就是我的結果。

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(() => {
            return transferFile(theFile); //function returns a promise
        });
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

正如以前的答案所暗示的那樣,使用:

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(transferFile(theFile));
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

在開始另一個之前沒有等待傳輸完成,並且“所有文件傳輸”文本甚至在第一個文件傳輸開始之前就出現了。

不確定我做錯了什么,但想分享對我有用的東西。

編輯:自從我寫了這篇文章,我現在明白為什么第一個版本不起作用了。 then() 需要一個返回承諾的函數 所以,你應該傳入不帶括號的函數名! 現在,我的函數需要一個參數,所以我需要包含在一個不帶參數的匿名函數中!

您還可以使用遞歸函數通過異步函數按順序處理可迭代對象。 例如,給定要使用異步函數someAsyncFunction()處理的數組a

 var a = [1, 2, 3, 4, 5, 6] function someAsyncFunction(n) { return new Promise((resolve, reject) => { setTimeout(() => { console.log("someAsyncFunction: ", n) resolve(n) }, Math.random() * 1500) }) } //You can run each array sequentially with: function sequential(arr, index = 0) { if (index >= arr.length) return Promise.resolve() return someAsyncFunction(arr[index]) .then(r => { console.log("got value: ", r) return sequential(arr, index + 1) }) } sequential(a).then(() => console.log("done"))

只是為了詳細說明@Bergi 的答案(非常簡潔,但很難理解;)

這段代碼將運行數組中的每一項,並將下一個“then chain”添加到最后;

function eachorder(prev,order) {
        return prev.then(function() {
          return get_order(order)
            .then(check_order)
            .then(update_order);
        });
    }
orderArray.reduce(eachorder,Promise.resolve());

希望這是有道理的。

使用async await可以輕松地按順序執行一組 promise:

let a = [promise1, promise2, promise3];

async function func() {
  for(let i=0; i<a.length; i++){
    await a[i]();
  }  
}

func();

注意:在上面的實現中,如果一個promise被拒絕了,剩下的就不會被執行。如果你想讓你所有的promise都被執行,那么包裝你的await a[i](); 里面try catch

平行線

看這個例子

const resolveAfterTimeout = async i => {
  return new Promise(resolve => {
    console.log("CALLED");
    setTimeout(() => {
      resolve("RESOLVED", i);
    }, 5000);
  });
};

const call = async () => {
  const res = await Promise.all([
    resolveAfterTimeout(1),
    resolveAfterTimeout(2),
    resolveAfterTimeout(3),
    resolveAfterTimeout(4),
    resolveAfterTimeout(5),
    resolveAfterTimeout(6)
  ]);
  console.log({ res });
};

call();

通過運行代碼,它將為所有六個承諾控制台“調用”,當它們被解決時,它將在超時后同時控制台每 6 個響應

您可以通過 for 循環來完成。

異步函數返回承諾

async function createClient(client) {
    return await Client.create(client);
}

let clients = [client1, client2, client3];

如果您編寫以下代碼,則並行創建客戶端

const createdClientsArray = yield Promise.all(clients.map((client) =>
    createClient(client);
));

然后並行創建所有客戶端。 但是如果你想按順序創建客戶端,那么你應該使用 for 循環

const createdClientsArray = [];
for(let i = 0; i < clients.length; i++) {
    const createdClient = yield createClient(clients[i]);
    createdClientsArray.push(createdClient);
}

然后依次創建所有客戶端。

快樂編碼:)

Bergi 的回答幫助我使調用同步。我在下面添加了一個示例,我們在調用前一個函數之后調用每個函數。

function func1 (param1) {
    console.log("function1 : " + param1);
}
function func2 () {
    console.log("function2");
}
function func3 (param2, param3) {
    console.log("function3 : " + param2 + ", " + param3);
}

function func4 (param4) {
    console.log("function4 : " + param4);
}
param4 = "Kate";

//adding 3 functions to array

a=[
    ()=>func1("Hi"),
    ()=>func2(),
    ()=>func3("Lindsay",param4)
  ];

//adding 4th function

a.push(()=>func4("dad"));

//below does func1().then(func2).then(func3).then(func4)

a.reduce((p, fn) => p.then(fn), Promise.resolve());

我一直在使用 for of 來解決順序承諾。 我不確定它在這里是否有幫助,但這就是我一直在做的。

async function run() {
    for (let val of arr) {
        const res = await someQuery(val)
        console.log(val)
    }
}

run().then().catch()

這可能會回答您的部分問題。

是的,您可以按如下方式鏈接一組 promise 返回函數......(這會將每個函數的結果傳遞給下一個)。 您當然可以編輯它以將相同的參數(或不帶參數)傳遞給每個函數。

 function tester1(a) { return new Promise(function(done) { setTimeout(function() { done(a + 1); }, 1000); }) } function tester2(a) { return new Promise(function(done) { setTimeout(function() { done(a * 5); }, 1000); }) } function promise_chain(args, list, results) { return new Promise(function(done, errs) { var fn = list.shift(); if (results === undefined) results = []; if (typeof fn === 'function') { fn(args).then(function(result) { results.push(result); console.log(result); promise_chain(result, list, results).then(done); }, errs); } else { done(results); } }); } promise_chain(0, [tester1, tester2, tester1, tester2, tester2]).then(console.log.bind(console), console.error.bind(console));

我在嘗試解決 NodeJS 中的一個問題時偶然發現了這個頁面:文件塊的重組。 基本上:我有一個文件名數組。 我需要以正確的順序附加所有這些文件,以創建一個大文件。 我必須異步執行此操作。

Node 的“fs”模塊確實提供了 appendFileSync,但我不想在此操作期間阻止服務器。 我想使用 fs.promises 模塊並找到一種方法將這些東西鏈接在一起。 此頁面上的示例對我來說不太適用,因為我實際上需要兩個操作: fsPromises.read() 讀取文件塊,以及 fsPromises.appendFile() 連接到目標文件。 也許如果我使用 javascript 更好,我可以讓以前的答案對我有用。 ;-)

我偶然發現了這個...... https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/ ......我能夠一起破解一個有效的解決方案。

域名注冊地址:

/**
 * sequentially append a list of files into a specified destination file
 */
exports.append_files = function (destinationFile, arrayOfFilenames) {
    return arrayOfFilenames.reduce((previousPromise, currentFile) => {
        return previousPromise.then(() => {
            return fsPromises.readFile(currentFile).then(fileContents => {
                return fsPromises.appendFile(destinationFile, fileContents);
            });
        });
    }, Promise.resolve());
};

這是一個茉莉花單元測試:

const fsPromises = require('fs').promises;
const fsUtils = require( ... );
const TEMPDIR = 'temp';

describe("test append_files", function() {
    it('append_files should work', async function(done) {
        try {
            // setup: create some files
            await fsPromises.mkdir(TEMPDIR);
            await fsPromises.writeFile(path.join(TEMPDIR, '1'), 'one');
            await fsPromises.writeFile(path.join(TEMPDIR, '2'), 'two');
            await fsPromises.writeFile(path.join(TEMPDIR, '3'), 'three');
            await fsPromises.writeFile(path.join(TEMPDIR, '4'), 'four');
            await fsPromises.writeFile(path.join(TEMPDIR, '5'), 'five');

            const filenameArray = [];
            for (var i=1; i < 6; i++) {
                filenameArray.push(path.join(TEMPDIR, i.toString()));
            }

            const DESTFILE = path.join(TEMPDIR, 'final');
            await fsUtils.append_files(DESTFILE, filenameArray);

            // confirm "final" file exists    
            const fsStat = await fsPromises.stat(DESTFILE);
            expect(fsStat.isFile()).toBeTruthy();

            // confirm content of the "final" file
            const expectedContent = new Buffer('onetwothreefourfive', 'utf8');
            var fileContents = await fsPromises.readFile(DESTFILE);
            expect(fileContents).toEqual(expectedContent);

            done();
        }
        catch (err) {
            fail(err);
        }
        finally {
        }
    });
});

我希望它可以幫助某人。

看到這個樣本

Promise.all並行工作

const { range, random, forEach, delay} = require("lodash");  
const run = id => {
    console.log(`Start Task ${id}`);
    let prom = new Promise((resolve, reject) => {
        delay(() => {
            console.log(`Finish Task ${id}`);
            resolve(id);
        }, random(2000, 15000));
    });
    return prom;
}


const exec = () => {
    let proms = []; 
    forEach(range(1,10), (id,index) => {
        proms.push(run(id));
    });
    let allPromis = Promise.all(proms); 
    allPromis.then(
        res => { 
            forEach(res, v => console.log(v));
        }
    );
}

exec();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM