我想在我的嵌入式設備上使用這個功能(在我的筆記本電腦上運行良好)或類似的東西。但是有兩個問題:
- 我們設備上的 Node.JS 太舊了,以至于 JavaScript 不支持 async / await。鑒于我們在該領域的數百個單位,更新可能是不切實際的。
- 即使這不是問題,我們還有另一個問題:檔案可能有數十兆位元組大小,不能一次全部放入記憶體中。而這個 for-await 回圈將或多或少同時啟動數千個異步任務。
async function sendOldData(pathname) {
const reader = ReadLine.createInterface({
input: fs.createReadStream(pathname),
crlfDelay: Infinity
})
for await (const line of reader) {
record = JSON.parse(line);
sendOldRecord(record);
}
}
function sendOldRecord(record) {...}
Promise 在這個舊版本中有效。我確信使用 Promises 順序執行此操作有一種優雅的語法:
read one line
massage its data
send that data to our server
順序但異步,以便在將資料發送到服務器時不會阻塞 JavaScript 事件回圈。
拜托,有人可以建議在我過時的 JavaScript 中執行此操作的正確語法嗎?
uj5u.com熱心網友回復:
建立一個佇列,以便將下一個從陣列中取出
function foo() {
const reader = [
'{"foo": 1}',
'{"foo": 2}',
'{"foo": 3}',
'{"foo": 4}',
'{"foo": 5}',
];
function doNext() {
if (!reader.length) {
console.log('done');
return;
}
const line = reader.shift();
const record = JSON.parse(line);
sendOldRecord(record, doNext);
}
doNext();
}
function sendOldRecord(record, done) {
console.log(record);
// what ever your async task is
window.setTimeout(function () {
done();
}, Math.floor(2000 * Math.random()));
}
foo();
uj5u.com熱心網友回復:
基本上你可以使用功能方法來實作這一點:
const arrayOfValues = [1,2,3,4,5];
const chainOfPromises = arrayOfValues.reduce((acc, item) => {
return acc.then((result) => {
// Here you can add your logic for parsing/sending request
// And here you are chaining next promise request
return yourAsyncFunction(item);
})
}, Promise.resolve());
// Basically this will do
// Promise.resolve().then(_ => yourAsyncFunction(1)).then(_ => yourAsyncFunction(2)) and so on...
// Start
chainOfPromises.then();
uj5u.com熱心網友回復:
問題
readline流和異步處理讓它們很好地協同作業并處理所有可能的錯誤情況有點痛苦,而且對于模塊來說情況更糟。由于您似乎在說您不能將for await ()構造用于可讀性(即使它受到支持,也存在各種問題),所以事情甚至更復雜一些。
on 流的主要問題readline.createInterface()是它讀取檔案的一個塊,決議該塊以獲得完整的行,然后在緊密的 for 回圈中同步發送所有行。
您可以從字面上看到這里的代碼:
for (let n = 0; n < lines.length; n ) this[kOnLine](lines[n]);
的實作是kOnLine這樣的:
this.emit('line', line);
所以,這是一個緊密的for回圈,它會發出它讀出的所有行。所以......如果你在回應line事件時嘗試做一些異步的事情,當你點擊一個await或異步回呼的那一刻,這個 readline 代碼將line在你完成處理前一個事件之前發送下一個事件。這使得line按順序對事件進行異步處理變得很痛苦,您在開始下一行之前完成異步處理一行。IMO,這是一個非常失敗的設計,因為它只適用于同步處理。你會注意到這個for回圈也不關心 readline 物件是否被暫停。它只是抽出它擁有的所有線路,而不考慮任何事情。
討論可能的解決方案
那么,該怎么辦呢。對此的修復的一部分是在 readline 的異步迭代器介面中(但它還有其他問題,我已經提交了錯誤)。但是,您的問題的假設似乎是您不能使用該異步迭代器介面,因為您的設備可能有較舊版本的 nodejs。如果是這種情況,那么我只知道兩種選擇:
- 完全放棄該
readline.createInterface()功能,要么使用第 3 方模塊,要么進行自己的線路邊界處理。 - 用您自己的代碼覆寫
line事件,該代碼支持異步處理行,而不會在仍在處理前一行的程序中獲取下一行。
一個解法
我已經為選項 #2 撰寫了一個實作,用您自己的代碼覆寫了 line 事件。在我的實作中,我們只承認在我們異步處理前幾行期間行事件將到達,但不是通知您那時,輸入流被暫停并且這些“早期”行被排隊。使用此解決方案,readline 代碼將從輸入流中讀取一大塊資料,將其決議為完整的行,同步發送line這些完整行的所有事件。但是,在收到第一行事件后,我們將暫停輸入流并啟動后續行事件的排隊。因此,您可以異步處理一行,并且在您請求下一行之前不會得到另一行。
This code has a different way of communicating incoming lines to your code. Since we're in the age of promises for asynchronous code, I've added a promise-based reader.getNextLine() function to the reader object.
This lets you write code like this:
import fs from 'fs';
async function run(filename) {
let reader = createLineReader({
input: fs.createReadStream(filename),
crlfDelay: Infinity
});
let line;
let cntr = 0;
while ((line = await reader.getNextLine()) !== null) {
// simulate some asynchronous operation in the processing of the line
console.log(`${ cntr}: ${line}`);
await processLine(line);
}
}
run("temp.txt").then(result => {
console.log("done");
}).catch(err => {
console.log(err);
});
And, here's the implementation of createLineReader():
import * as ReadLine from 'readline';
function createLineReader(options) {
const stream = options.input;
const reader = ReadLine.createInterface(options);
// state machine variables
let latchedErr = null;
let isPaused = false;
let readerClosed = false;
const queuedLines = [];
// resolves with line
// resolves with null if no more lines
// rejects with error
reader.getNextLine = async function() {
if (latchedErr) {
// once we get an error, we're done
throw latchedErr;
} else if (queuedLines.length) {
// if something in the queue, return the oldest from the queue
const line = queuedLines.shift();
if (queuedLines.length === 0 && isPaused) {
reader.resume();
}
return line;
} else if (readerClosed) {
// if nothing in the queue and the reader is closed, then signify end of data
return null;
} else {
// waiting for more line data to arrive
return new Promise((resolve, reject) => {
function clear() {
reader.off('error', errorListener);
reader.off('queued', queuedListener);
reader.off('done', doneListener);
}
function queuedListener() {
clear();
resolve(queuedLines.shift());
}
function errorListener(e) {
clear();
reject(e);
}
function doneListener() {
clear();
resolve(null);
}
reader.once('queued', queuedListener);
reader.once('error', errorListener);
reader.once('done', doneListener);
});
}
}
reader.on('pause', () => {
isPaused = true;
}).on('resume', () => {
isPaused = false;
}).on('line', line => {
queuedLines.push(line);
if (!isPaused) {
reader.pause();
}
// tell any queue listener that something was just added to the queue
reader.emit('queued');
}).on('close', () => {
readerClosed = true;
if (queuedLines.length === 0) {
reader.emit('done');
}
});
return reader;
}
Explanation
Internally, the implementation takes each new line event and puts it into a queue. Then, reader.getNextLine() just pulls items from the queue or waits (with a promise) for the queue to get something put in it.
During operation, the readline object will get a chunk of data from your readstream, it will parse that into whole lines. The whole lines will all get added to the queue (via line events). The readstream will be paused so it won't generate any more lines until the queue has been drained.
When the queue becomes empty, the readstream will be resumed so it can send more data to the reader object.
This is scalable to very large files because it will only queue the whole lines found in one chunk of the file being read. Once those lines are queued, the input stream is paused so it won't put more into the queue. After the queue is drained, the inputs stream is resumed so it can send more data and repeat...
Any errors in the readstream will trigger an error event on the readline object which will either reject a reader.getNextLine() that is already waiting for the next line or will reject the next time reader.getNextLine() is called.
Disclaimers
This has only been tested with file-based readstreams.
I would not recommend having more than one reader.getNextLine() in process at once as this code does not anticipate that and it's not even clear what that should do.
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/451759.html
標籤:javascript 异步 承诺
