Preface
Concepts related to asynchronization can be referenced Shallow js asynchronous events .The benefit of Javascript's single-threaded mechanism is that when code is run, it ensures that variables accessed by code are not interfered with by other threads.Imagine if another thread modifies an array while you're traversing it, and that's a mess.The use of setTimeout/setInterval, browser-side ajax, and IO in Node are all based on a correct understanding of asynchronous (e.g. Event loop, Event queue).
Asynchronous Cycle
Assuming I have an array with file names, I want to read the files in turn until the first successful reading of a file returns the contents of the file.That is, if the array containing the file name is ['a.txt','b.txt'], read a.txt first, and if the content of a.txt is returned successfully.Read failed words read b.txt.And so on.Node provides synchronization methods when reading files readFileSync Follow Asynchronous Method readFile.
Suppose we have two files: a.txt (the file content is also a.txt) and b.txt (the file content is also b.txt).
Synchronized writing is simple:
let fs = require('fs'), path = require('path'); function readOneSync(files) { for(let i = 0, len = files.length; i < len; i++) { try { return fs.readFileSync(path.join(__dirname, files[i]), 'utf8'); } catch(e) { //ignore } } throw new Error('all fail'); } console.log(readOneSync(['a.txt', 'b.txt'])); //a.txt console.log(readOneSync(['filenotexist', 'b.txt'])); //b.txt
The biggest problem with synchronous writing is that it can block other event processing in the event queue.Assume that the file being read is very long and will cause the app to be unresponsive during this time.Asynchronous IO avoids this problem.However, the order of the callbacks needs to be handled in the callback (i.e. judgment and operation on whether to read the next file in the callback read from the previous file).
let fs = require('fs'), path = require('path'); function readOne(files, cb) { function next(index) { let fileName = files[index]; fs.readFile(path.join(__dirname, fileName), 'utf8', (err, data) => { if(err) { return next(index + 1); } else { return cb(data); } }); } next(0); } readOne(['a.txt', 'b.txt'], console.log); //a.txt readOne(['filenotexist', 'b.txt'], console.log); //b.txt
Asynchronous writing requires a callback function (i.e. cb) to be passed to operate on the returned results.A method, next, is also defined to recursively call itself (i.e. next) to read the next file if reading the file fails.
Initiate multiple asynchronous requests simultaneously
Suppose I now have an array with file names that I want to read asynchronously at the same time.A successful callback is invoked when all reads are successful.A failed callback is invoked if either fails.
let fs = require('fs'), path = require('path'); function readAllV1(files, onsuccess, onfail) { let result = []; files.forEach(file => { fs.readFile(path.join(__dirname, file), 'utf8', (err, data) => { if(err) { onfail(err); } else { result.push(data); if(result.length === files.length) { onsuccess(result); } } }); }); } readAllV1(['a.txt', 'b.txt'], console.log, console.log); //Result Uncertainty
There is a problem here.Because the operation of reading a file is triggered asynchronously at the same time, depending on the time the file is read, the handler of the file that has been read earlier is put in the event queue first.This causes the contents of the final result array to not correspond to the file name of the files.For example, suppose that files are ['a.txt','b.txt'], a.txt is 100M, b.txt is 10kb, and two read asynchronously at the same time, because b.txt is smaller, the callbacks in the readFile corresponding to b.txt will be in the event queue before a.txt.When a callback reading b.txt runs, result.push(data) first inserts the contents of b.txt into the result.The final result returned will be [${b.txt file content}, ${a.txt file content}].When ordering the returned results, we can simply modify the following:
let fs = require('fs'), path = require('path'); function readAllV2(files, onsuccess, onfail) { let result = []; files.forEach((file, index) => { fs.readFile(path.join(__dirname, file), 'utf8', (err, data) => { if(err) { onfail(err); } else { result[index] = data; if(result.length === files.length) { onsuccess(result); } } }); }); } readAllV2(['a.txt', 'b.txt'], console.log, console.log); //Result Uncertainty
It looks like something is wrong with the wood.But!
let arr = []; arr[1] = 'a'; console.log(arr.length); //2
According to the implementation of readAllV2, suppose that b.txt is finished before a.txt is finished, and we set result[1] = data.If (result.length == files.length) is true at this point, a successful callback is called directly.So we can't rely on result.length for checking.
let fs = require('fs'), path = require('path'); function readAllV3(files, onsuccess, onfail) { let result = [], counter = 0; files.forEach((file, index) => { fs.readFile(path.join(__dirname, file), 'utf8', (err, data) => { if(err) { onfail(err); } else { result[index] = data; counter++; if(counter === files.length) { onsuccess(result); } } }); }); } readAllV3(['a.txt', 'b.txt'], console.log, console.log); //[ 'a.txt', 'b.txt' ]
If you're familiar with Promise, there's one in Promise Promise.all That's what happens.
Do not mix synchronous and asynchronous callback functions, try to maintain interface consistency
Suppose we implement a read file method with a cache.We read files asynchronously when there is no time in the cache, or fetch them directly from the cache.
let fs = require('fs'), path = require('path'), cache = {}; function readWithCacheV1(file, onsuccess, onfail) { if(cache[file]) { onsuccess(cache[file]); } else { fs.readFile(path.join(__dirname, file), 'utf8', (err, data) => { if(err) { onfail(err); } else { cache[file] = data; onsuccess(data); } }); } }
Look specifically at the above implementation:
When there is data in the cache, a successful callback onsuccess is called synchronously.
cache['a.txt'] = 'hello'; //mock the data in the cache readWithCacheV1('a.txt', console.log);//Synchronize calls, wait until the call is complete before proceeding to the next statement console.log('after you'); //Output results: hello after you
When the cache has no data, it is called asynchronously.
readWithCacheV1('a.txt', console.log);//Cache has no data.Asynchronous call console.log('after you'); //Output results: after you hello
This results in inconsistencies, and unpredictable execution sequence of programs can easily lead to bug crashes.To maintain consistency, you can uniformly make asynchronous calls wrapped in setTimeout.
let fs = require('fs'), path = require('path'), cache = {}; function readWithCacheV2(file, onsuccess, onfail) { if(cache[file]) { setTimeout(onsuccess.bind(null, cache[file]),0); } else { fs.readFile(path.join(__dirname, file), 'utf8', (err, data) => { if(err) { onfail(err); } else { cache[file] = data; onsuccess(data); } }); } }
There are two scenarios of cache and no cache when running again:
Called asynchronously through setTimeout when there is data in the cache
cache['a.txt'] = 'hello'; readWithCacheV2('a.txt', console.log); console.log('after you'); //Output results: after you hello
When the cache has no data,
readWithCacheV2('a.txt', console.log); console.log('after you'); //Output results: after you hello
Reference
Code
Notice
If you feel like this Repo For your reward, please "Star" to support the landlord.
If you want to keep an eye on the latest series of articles from landlords, Please subscribe to Watch