Node中最多見的內置模塊 Filesystem(fs), 該模塊提供了處理文件和目錄的函數node
// 異步版本 var fs = requier('fs') fs.readFile('file.text', 'utf8', function(error, text) { if(error) { throw error } console.log('The file contained:', text) }) //同步版本 fs.readFileSync('file.text', 'utf8')
異步回調函數中一般遵循異步優先, 即第一個參數接收可能錯誤的對象, 其他參數接收結果
使用同步函數較爲節省代碼,再簡單的腳本中很是適用,可是異步函數會帶來額外的速度提高,減小延遲面試
硬連接是指經過索引節點來進行連接。在Linux的文件系統中,全部文件都會分配一個索引節點編號Inode,它是文件在系統中的惟一標識,文件的實際數據放置在數據區域(data block),INode存儲着文件參數信息(元數據metadata),好比建立時間、修改時間、文件大小、屬主、歸屬的用戶組、讀寫權限、數據所在block號等,多個文件名能夠指向同一索引節點(Inode)。windows
硬連接只能在同一文件系統(盤)中的文件之間進行連接,不能對目錄進行建立。只要文件的索引節點還有一個以上的連接,其中一個連接更改文件內容,其他連接讀取文件內容也發生改變,只刪除其中一個連接並不影響索引節點自己和其餘的連接(數據的實體並未刪除),只有當最後一個連接被刪除後,此時若是有新數據要存儲到磁盤上,被刪除的文件的數據塊及目錄的連接纔會被釋放,空間被新數據暫用覆蓋。數組
軟連接(也叫符號連接),相似於windows系統中的快捷方式,與硬連接不一樣,軟連接就是一個普通文件,只是數據塊內容有點特殊,文件用戶數據塊中存放的內容是另外一文件的路徑名的指向,經過這個方式能夠快速定位到軟鏈接所指向的源文件實體。源文件刪除,軟連接也會失效,軟連接能夠跨文件系統對文件或目錄建立。promise
執行 readFile 函數 返回Promisedom
var fs = require('fs') function readFilePromise(...args) { return new Promise((resolve, reject) => { fs.readFile(...args, (err, data) => { if (err) { reject(err) } else { resolve(data) } }) }) } readFilePromise('a.js').then(data => { }).catch(err => { })
執行 writeFile 函數 返回Promise異步
var fs = require('fs') function writeFilePromise(...args) { return new Promise((resolve, reject) => { fs.writeFile(...args, (err) => { if (err) { resolve(err) } else { reject() } }) }) } writeFilePromise('a.js').then(data => { }).catch(err => { })
將一個基於回調的函數轉爲一個返回 Promise 的函數函數
function promisify(callbackBasedFunction) { return function(...args) { return new Promise((resolve, reject) => { callbackBasedFunction(...args,(err, data) => { if (err) { reject(err) } else { resolve(data) } }) }) } } readFilePromise = promisify(fs.readFile) writeFilePromise = promisify(fs.writeFile) statunlinkPromise = promisify(fs.stat) unlinkPromise = promisify(fs.unlink)
將一個基於 Promise 的函數轉爲一個返回回調的函數優化
function callbackify(promiseBased) { return function (...args) { var cb = args.pop() promiseBased(...args).then(val => { cb(null, val) }, reason => { cb(reason) }) } }
固然啦, 這兩個函數在標準庫中已經集成好了, 就是 utilsui
var fs = require('fs') var utils = require('utils') var readFilePromise = utils.promisify(fs.readFile) var readFile = utils.callbackify(readFilePromise)
一個一個轉仍是有點麻煩, 如今的 node 已經提供了promise 版本的 fs 模塊
fs = require('fs').promises fs.readFile('a.txt').then().catch()
接收一個文件夾路徑,返回這個文件夾裏面的全部文件名,須要遞歸的獲得全部的文件名 並放在一個一維數組裏返回
須要寫三個版本:
const fs = require('fs') const fsp = fs.promises function listAllFilesSync(path) { var stat = fs.statSync(path) var result = [] if (stat.isFile()) { return [path] // 若是路徑類型是文件,直接返回 } else { var entries = fs.readdirSync(path, { withFileTypes: true }) // 讀取全部文件, withFileTypes 生成的數組將填充 fs.Dirent 對象,而不是字符串 entries.forEach(entry => { var fullPath = path + '/' + entry.name // 新的路徑爲原來的path接上新的文件夾名 var files = listAllFilesSync(fullPath) // 遞歸, 返回的數組全都push到result中 result.push(...files) }); return result } } console.log(listAllFilesSync('./'))
function listAllFilesPromise(path) { return fsp.stat(path).then(stat => { if (stat.isFile()) { return [path] } else { return fsp.readdir(path, {withFileTypes: true}).then(entries => { return Promise.all(entries.map(entry => { return listAllFilesPromise(path + '/' + entry.name) })).then(arrays => { return [].concat(...arrays) }) }) } }) } listAllFilesPromise('./').then(console.log)
function listAllFilesCallback(path, callback) { fs.stat(path, (err, stat) => { if (stat.isFile()) { callback([path]) } else { fs.readdir(path, {withFileTypes: true}, (err, entries) => { var result = [] var count = 0 if (entries.length == 0) { callback([]) // 當文件夾爲空時,直接返回,不走forEach } entries.forEach((entry, i) => { var fullPath = path + '/' + entry.name listAllFilesCallback(fullPath, (files) => { result[i] = files count++ if (count == entries.length) { callback([].concat(...result)) } }) }) }) } }) } listAllFilesCallback('./', console.log)
以上的三種方法: 同步版本時間效率不高, promise 版本 return 過多比較繁瑣, 異步版本 return 很少 可是嵌套層級不靈活
有沒有一種方法能夠兼具三者的優勢呢?
有! 將生成器函數和promise函數組合, 便可優化promise異步代碼的書寫
function * natureNumber(n) { for(var i = 0; i < n; i++) { var x = yield i console.log(x) } } var iter = natureNumber(5) iter.next() // {value: 0, done: false} iter.next(88) // 88 {value: 1, done: false} iter.next(99) // 00 {value: 2, done: false} iter.return(111) // {value: 111, done: true}
咱們先用一個 xhr 結合生成器函數舉例
function get(url) { return new Promise(resolve => { var xhr = new XMLHttpRequest() xhr.open('get', url) xhr.onload = () => resolve(xhr.responseText) xhr.send() }) } function * foo () { var x = yield get('/') console.log(x) } var iter = foo() obj = iter.next() // {value: Promise, done: false} obj.value.then(val => iter.next(val)) // 返回頁面內容
yield的優勢是能夠在執行中等待, 等待的時間由函數執行時間決定
爲了方便調試,接下來的舉例會用setTimeout來模擬一個異步的請求,當返回多個值時:
function squareAsync(x) { return new Promise(resolve => { setTimeout(() => resolve(x * x), 1000 + Math.random() * 2000) }) } function * foo () { var x = yield squareAsync(2) console.log(x) var y = yield squareAsync(3) console.log(y) var z = yield squareAsync(4) console.log(z) } var iter = foo() iter.next().value.then(val =>{ // yield 返回 promise iter.next(val).value.then(val =>{ // 4 iter.next(val).value.then(val =>{ // 9 iter.next(val) // 16 }) }) })
是否是發現了點什麼, 這麼寫重複度有點高,能夠進行封裝:
var iter = foo() var generated = iter.next() start() function start(val) { if (!generated.done) { generated.value.then(val => { generated = iter.next(val) start(val) }) } }
異步遞歸, 把一個老是生成 promise 的生成器函數, 在 promise 執行時等待, 然後 promise 獲得結果以後繼續執行, 並將 promise 的結果賦值給變量。 那麼, 若是 promise 返回失敗了怎麼辦?
function * foo () { var x = yield squareAsync(2) console.log(x) try { var y = yield Promise.reject(3) console.log(y) } catch(e) { console.log('error', e) } var z = yield squareAsync(4) console.log(z) } var iter = foo() var generated = iter.next() step() function step() { if (!generated.done) { generated.value.then(val => { generated = iter.next(val) step() }, reason => { generated = iter.throw(reason) step() }) } }
利用這種方式, 能夠將異步的函數寫成相似同步的方式, 在代碼可讀性上有了很大的提高, 固然了還能夠再進行封裝, 方便使用, 同時能夠將分步執行的函數也做爲promise執行, 這樣在全部分步執行的步驟結束後咱們也能夠拿到一個返回值:
run(function * foo () { var x = yield squareAsync(2) console.log(x) try { var y = yield Promise.reject(3) console.log(y) } catch(e) { console.log('error', e) } var z = yield squareAsync(4) console.log(z) }).then((val) => { console.log(val) }) function run(generatorFunction) { return new Promise((resolve, reject) => { var iter = generatorFunction() var generated = iter.next() step() function step() { if (!generated.done) { generated.value.then(val => { generated = iter.next(val) step() }, reason => { generated = iter.throw(reason) step() }) } else { Promise.resolve(generated.value).then(resolve, reject) //此時的generated.value是生成器函數的返回值, 同時返回的函數也有多是promise,因此返回一個promise } } }) }
接下來, 還有最後一種狀況須要考慮, 即在生成器函數中沒有 try 應該怎麼辦, 好比下面這樣:
run(function * foo () { var x = yield squareAsync(2) console.log(x) try { var y = yield Promise.reject(3) console.log(y) } catch(e) { console.log('error', e) } var m = yield Promise.reject(4)// yield 拋出錯誤,可是沒有包try console.log(m) var z = yield squareAsync(5) console.log(z) }).then((val) => { console.log(val) })
在這種狀況下, yield的錯誤會拋出到foo函數的外面, 若是不處理的話頗有可能就跑到控制檯去了。 固然不止這一種狀況可能出錯, 好比 console.log(m) 不當心寫成了 console.log(m(), 會在next的時候產生執行錯誤, 此要想解決錯誤, 應該在run中調用try/catch
function run(generatorFunction) { return new Promise((resolve, reject) => { var iter = generatorFunction() var generated try { generated = iter.next() step() } catch(e) { reject(e) } function step() { if (!generated.done) { generated.value.then(val => { try { generated = iter.next(val) step() } catch(e) { reject(e) } }, reason => { try { generated = iter.throw(reason) step() } catch(e) { reject(e) } }) } else { Promise.resolve(generated.value).then(resolve, reject) } } }) }
上面這段代碼, 在面試中有可能會被問到, 須要能寫出來剛剛咱們只是考慮生成器函數調用普通函數, 若是生成器函數調用的是另外一個生成器函數應該怎麼寫呢, 那個有點複雜, 咱們如今先不考慮。