2017-08-15 56 views
0

我們長期的Python和PHP編碼器具有整齊的同步代碼位(示例如下)。大部分功能都有異步對應。我們真的想'獲得'Javascript和Node的力量,並且認爲這是異步node.js加快速度並吹噓的理想情況。重構同步代碼以釋放node.js異步性的力量

什麼是重構以下利用異步節點的教科書方式? Async/awaitpromise.all?怎麼樣? (使用節點8.4.0的向後兼容性不是一個問題。)

var fs = require('fs'); 

// This could list over 10,000 files of various size 
const fileList = ['file1', 'file2', 'file3']; 

const getCdate = file => fs.statSync(file).ctime; // Has async method 

const getFSize = file => fs.statSync(file).size; // Has async method 

// Can be async through file streams (see resources below) 
const getMd5 = (file) => { 
    let fileData = new Buffer(0); 
    fileData = fs.readFileSync(file); 
    const hash = crypto.createHash('md5'); 
    hash.update(fileData); 
    return hash.digest('hex'); 
}; 

let filesObj = fileList.map(file => [file, { 
    datetime: getCdate(file), 
    filesize: getFSize(file), 
    md5hash: getMd5(file), 
}]); 

console.log(filesObj); 

注:

  • 我們需要保持功能的模塊化和可重複使用。
  • 有比更多的功能
  • 大多數函數可以被重寫爲異步,有些不能。
  • 理想情況下,我們需要保持原來的訂單fileList
  • 理想情況下,我們希望使用最新的Node和JS功能 - 不依賴於外部模塊。爲獲得MD5異步

什錦文件流的方法:

+0

沒有解釋的downvote是無用的。請幫助改善問題。 – Trees4theForest

+1

我希望downvote彈出一個框,要求用戶輸入至少一個簡短的理由。 – Chev

回答

0

我會讓getCDategetFSize,並且getMd5所有的異步和promisified然後包他們在另一個異步hronous promise-returning函數,這裏叫做statFile

function statFile(file) { 
    return Promise.all([ 
     getCDate(file), 
     getFSize(file), 
     getMd5(file) 
    ]).then((datetime, filesize, md5hash) => ({datetime, filesize, md5hash})) 
    .catch(/*handle error*/); 
} 

然後你可以改變你的映射功能

const promises = fileList.map(statFile); 

然後,它使用簡單,Promise.all:

Promise.all(promises) 
    .then(filesObj => /*do something*/) 
    .catch(err => /*handle error*/) 

這留下的東西模塊化,不需要異步/ AWAIT ,允許您將額外的功能插入statFile,並保留您的文件順序。

+1

要從箭頭函數返回對象,您需要將其包裝在括號中,以便不會將括號打開代碼塊。 '(datetime,filesize,md5hash)=>({datetime,filesize,md5hash});':) – Chev

+1

@Chev謝謝,我不知道這個問題的答案。 –

1

您可以通過多種不同的方式異步處理此代碼。您可以使用節點async庫更優雅地處理所有回調。如果你不想潛入承諾那麼這就是「簡單」的選擇。我容易引用,因爲如果你足夠了解它們,承諾實際上更容易。異步庫很有幫助,但它仍然存在很多錯誤傳播的方式,並且有很多樣板代碼需要將所有的調用包裝進去。

更好的方法是使用promise 。異步/等待仍然很新。沒有像Bable或Typescript這樣的預處理器,甚至在節點7中都不支持(不確定節點8)。而且,async/await無論如何都會使用承諾。

這是我會怎麼使用承諾做到這一點,甚至包括了最大性能文件統計緩存:

const fs = require('fs'); 
const crypto = require('crypto'); 
const Promise = require('bluebird'); 
const fileList = ['file1', 'file2', 'file3']; 

// Use Bluebird's Promise.promisifyAll utility to turn all of fs' 
// async functions into promise returning versions of them. 
// The new promise-enabled methods will have the same name but with 
// a suffix of "Async". Ex: fs.stat will be fs.statAsync. 
Promise.promisifyAll(fs); 

// Create a cache to store the file if we're planning to get multiple 
// stats from it. 
let cache = { 
    fileName: null, 
    fileStats: null 
}; 
const getFileStats = (fileName, prop) => { 
    if (cache.fileName === fileName) { 
    return cache.fileStats[prop]; 
    } 
    // Return a promise that eventually resolves to the data we're after 
    // but also stores fileStats in our cache for future calls. 
    return fs.statAsync(fileName).then(fileStats => { 
    cache.fileName = fileName; 
    cache.fileStats = fileStats; 
    return fileStats[prop]; 
    }) 
}; 

const getMd5Hash = file => { 
    // Return a promise that eventually resolves to the hash we're after. 
    return fs.readFileAsync(file).then(fileData => { 
    const hash = crypto.createHash('md5'); 
    hash.update(fileData); 
    return hash.digest('hex'); 
    }); 
}; 

// Create a promise that immediately resolves with our fileList array. 
// Use Bluebird's Promise.map utility. Works very similar to Array.map 
// except it expects all array entries to be promises that will 
// eventually be resolved to the data we want. 
let results = Promise.resolve(fileList).map(fileName => { 
    return Promise.all([ 

    // This first gets a promise that starts resolving file stats 
    // asynchronously. When the promise resolves it will store file 
    // stats in a cache and then return the stats value we're after. 
    // Note that the final return is not a promise, but returning raw 
    // values from promise handlers implicitly does 
    // Promise.resolve(rawValue) 
    getFileStats(fileName, 'ctime'), 

    // This one will not return a promise. It will see cached file 
    // stats for our file and return the stats value from the cache 
    // instead. Since it's being returned into a Promise.all, it will 
    // be implicitly wrapped in Promise.resolve(rawValue) to fit the 
    // promise paradigm. 
    getFileStats(fileName, 'size'), 

    // First returns a promise that begins resolving the file data for 
    // our file. A promise handler in the function will then perform 
    // the operations we need to do on the file data in order to get 
    // the hash. The raw hash value is returned in the end and 
    // implicitly wrapped in Promise.resolve as well. 
    getMd5(file) 
    ]) 
    // .spread is a bluebird shortcut that replaces .then. If the value 
    // being resolved is an array (which it is because Promise.all will 
    // resolve an array containing the results in the same order as we 
    // listed the calls in the input array) then .spread will spread the 
    // values in that array out and pass them in as individual function 
    // parameters. 
    .spread((dateTime, fileSize, md5Hash) => [file, { dateTime, fileSize, md5Hash }]); 
}).catch(error => { 
    // Any errors returned by any of the Async functions in this promise 
    // chain will be propagated here. 
    console.log(error); 
}); 

這裏是再次的代碼,但沒有註釋,使其更容易查看:

const fs = require('fs'); 
const crypto = require('crypto'); 
const Promise = require('bluebird'); 
const fileList = ['file1', 'file2', 'file3']; 

Promise.promisifyAll(fs); 

let cache = { 
    fileName: null, 
    fileStats: null 
}; 
const getFileStats = (fileName, prop) => { 
    if (cache.fileName === fileName) { 
    return cache.fileStats[prop]; 
    } 
    return fs.statAsync(fileName).then(fileStats => { 
    cache.fileName = fileName; 
    cache.fileStats = fileStats; 
    return fileStats[prop]; 
    }) 
}; 

const getMd5Hash = file => { 
    return fs.readFileAsync(file).then(fileData => { 
    const hash = crypto.createHash('md5'); 
    hash.update(fileData); 
    return hash.digest('hex'); 
    }); 
}; 

let results = Promise.resolve(fileList).map(fileName => { 
    return Promise.all([ 
    getFileStats(fileName, 'ctime'), 
    getFileStats(fileName, 'size'), 
    getMd5(file) 
    ]).spread((dateTime, fileSize, md5Hash) => [file, { dateTime, fileSize, md5Hash }]); 
}).catch(console.log); 

最終結果將是一個數組喜歡它應該有希望匹配您的原碼的結果,而應執行的基準好得多:

[ 
    ['file1', { dateTime: 'data here', fileSize: 'data here', md5Hash: 'data here' }], 
    ['file2', { dateTime: 'data here', fileSize: 'data here', md5Hash: 'data here' }], 
    ['file3', { dateTime: 'data here', fileSize: 'data here', md5Hash: 'data here' }] 
] 

任何錯別字提前道歉。沒有時間或能力去實際運行這些。儘管我看了很多。


在發現async/await在7.6節點後,我決定昨晚玩一下它。對於不需要並行執行的遞歸異步任務,或者您希望可以同步寫入的嵌套異步任務,似乎最爲有用。對於你在這裏需要的東西,沒有任何令人興奮的方式來使用異步/等待,我可以看到,但有幾個地方的代碼會更乾淨地閱讀。這是代碼,但有一些異步/等待的便利。

const fs = require('fs'); 
const crypto = require('crypto'); 
const Promise = require('bluebird'); 
const fileList = ['file1', 'file2', 'file3']; 

Promise.promisifyAll(fs); 

let cache = { 
    fileName: null, 
    fileStats: null 
}; 
async function getFileStats (fileName, prop) { 
    if (cache.fileName === fileName) { 
    return cache.fileStats[prop]; 
    } 
    let fileStats = await fs.stat(fileName); 
    cache.fileName = fileName; 
    cache.fileStats = fileStats; 
    return fileStats[prop]; 
}; 

async function getMd5Hash (file) { 
    let fileData = await fs.readFileAsync(file); 
    const hash = crypto.createHash('md5'); 
    hash.update(fileData); 
    return hash.digest('hex'); 
}; 

let results = Promise.resolve(fileList).map(fileName => { 
    return Promise.all([ 
    getFileStats(fileName, 'ctime'), 
    getFileStats(fileName, 'size'), 
    getMd5(file) 
    ]).spread((dateTime, fileSize, md5Hash) => [file, { dateTime, fileSize, md5Hash }]); 
}).catch(console.log); 
+0

太棒了。感謝您的想法。 'async' /'await'是節點的一部分,因爲我相信7.6,諾言也是標準的。不要以爲我們需要藍鳥(不知道'promisfy'),我們寧願儘可能地做'本地'的東西(少模塊)。 – Trees4theForest

+0

基本承諾做了很多,但我更喜歡便利的方法。 '.spread','promisifyAll'和'Promise.map'。儘管節點現在支持基本的承諾,但藍鳥仍然常用。儘管你可以在沒有藍鳥的情況下完成大部分上述任務。 '.spread'可以只是'.then',你可以手動從結果數組中讀取數據。宣佈'fs'功能雖然會是一些樣板。 Bluebird的'Promise.map'必須用一個手工array.map替換爲一個promise數組。 – Chev

+0

也感謝您的異步/等待提示。不知道我已經可以檢查出來了! :D – Chev