Generator can pause the execution of a function and return the value of any expression.This feature allows Generator to have a variety of scenarios.
1. Synchronized representation of asynchronous operations
The Pause effect of the Generator function means that the asynchronous operation can be written in the yield expression until the next method is called.This is essentially equivalent to not having to write a callback function, since subsequent operations of an asynchronous operation can be placed under a yield expression until the next method is called.Therefore, an important practical significance of the Generator function is to handle asynchronous operations and override callback functions.
function* loadUI() { showLoadingScreen(); yield loadUIDataAsynchronously(); hideLoadingScreen(); } var loader = loadUI(); // Load UI loader.next() // Uninstall UI loader.next()
In the code above, the first time the loadUI function is called, it does not execute and only returns a traverse.The next time the next method is called on the traverser, the Loading interface is displayed and the data is loaded asynchronously.The Loading interface is hidden when the data loading is completed and the next method is used again.As you can see, the advantage of this writing is that all the logic of the Loading interface is encapsulated in a single function, so it's very clear to work step by step.
Ajax is a typical asynchronous operation, which can be expressed synchronously by deploying Ajax operations through the Generator function.
function* main() { var result = yield request("http://some.url"); var resp = JSON.parse(result); console.log(resp.value); } function request(url) { makeAjaxCall(url, function(response){ it.next(response); }); } var it = main(); it.next();
The main function of the code above is to get data through an Ajax operation.You can see that, with the exception of one more yield, it is written almost exactly like a synchronous operation.Note that the next method in the makeAjaxCall function must have a response parameter added, because the yield expression itself has no value and is always equal to undefined`.
Here is another example of reading a text file line by line through the Generator function.
function* numbers() { let file = new FileReader("numbers.txt"); try { while(!file.eof) { yield parseInt(file.readLine(), 10); } } finally { file.close(); } }
The code above opens a text file and uses a yield expression to read the file line by line manually.
2. Control Flow Management
If you have a multistep operation that is time consuming, using a callback function may be written as follows.
step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) { step4(value3, function(value4) { // Do something with value4 }); }); }); });
Use Promise to overwrite the code above.
Promise.resolve(step1) .then(step2) .then(step3) .then(step4) .then(function (value4) { // Do something with value4 }, function (error) { // Handle any error from step1 through step4 }) .done();
The code above has changed the callback function to a straight line execution, but adds a lot of Promise syntax.The Generator function further improves the code flow.
function* longRunningTask(value1) { try { var value2 = yield step1(value1); var value3 = yield step2(value2); var value4 = yield step3(value3); var value5 = yield step4(value4); // Do something with value4 } catch (e) { // Handle any error from step1 through step4 } }
Then, use a function to automate all the steps in order.
scheduler(longRunningTask(initialValue)); function scheduler(task) { var taskObj = task.next(task.value); // If the Generator function does not end, the call continues if (!taskObj.done) { task.value = taskObj.value scheduler(task); } }
Note that this practice above is only appropriate for synchronous operations, that is, all task s must be synchronous and no asynchronous operations can be performed.As soon as the code here gets a return value, it continues executing without judging when the asynchronous operation is complete.
Below, a more general method of control flow management is provided by utilizing the for...of loop, which automatically executes the yield command in turn.
let steps = [step1Func, step2Func, step3Func]; function *iterateSteps(steps){ for (var i=0; i< steps.length; i++){ var step = steps[i]; yield step(); } }
In the code above, the array steps encapsulates the steps of a task, and the Generator function iterateSteps adds the yield command to those steps in turn.
After breaking down a task into steps, you can also break down a project into multiple sequential tasks.
let jobs = [job1, job2, job3]; function* iterateJobs(jobs){ for (var i=0; i< jobs.length; i++){ var job = jobs[i]; yield* iterateSteps(job.steps); } }
In the code above, the array jobs encapsulates multiple tasks for a project, and the Generator function 1iterateJobs adds yield*` commands to those tasks in turn.
Finally, you can use the for...of loop to perform all the steps of all the tasks one at a time.
for (var step of iterateJobs(jobs)){ console.log(step.id); }
Again, the above practice can only be used when all steps are synchronous, not asynchronous.
for...of is essentially a while loop, so the code above essentially executes the following logic.
var it = iterateJobs(jobs); var res = it.next(); while (!res.done){ var result = res.value; // ... res = it.next(); }
3. Deploy Iterator interface
The Generator function allows you to deploy the Iterator interface on any object.
function* iterEntries(obj) { let keys = Object.keys(obj); for (let i=0; i < keys.length; i++) { let key = keys[i]; yield [key, obj[key]]; } } let myObj = { foo: 3, bar: 7 }; for (let [key, value] of iterEntries(myObj)) { console.log(key, value); } // foo 3 // bar 7
In the above code, myObj is a common object, and through the iterEntries function, there is an Iterator interface.That is, the next method can be deployed on any object.
Below is an example of deploying an Iterator interface to an array, although the array itself has this interface.
function* makeSimpleGenerator(array){ var nextIndex = 0; while(nextIndex < array.length){ yield array[nextIndex++]; } } var gen = makeSimpleGenerator(['yo', 'ya']); gen.next().value // 'yo' gen.next().value // 'ya' gen.next().done // true
4. As a data structure
Generator can be thought of as a data structure, or rather an array structure, because the Generator function can return a series of values, which means it can provide an array-like interface to any expression.
function *doStuff() { yield fs.readFile.bind(null, 'hello.txt'); yield fs.readFile.bind(null, 'world.txt'); yield fs.readFile.bind(null, 'and-such.txt'); }
The code above returns three functions in turn, but because the Generator function is used, you can handle the three returned functions as you would with arrays.
for (task of doStuff()) { // task is a function that can be used like a callback function }
In fact, if expressed in ES5, it is entirely possible to simulate this use of Generator using arrays.
function doStuff() { return [ fs.readFile.bind(null, 'hello.txt'), fs.readFile.bind(null, 'world.txt'), fs.readFile.bind(null, 'and-such.txt') ]; }
The above functions can be treated with the same for...of loop!By comparing the two phases, it is not difficult to see that Generator makes data or operations an array-like interface.
ES6 from Ruan Yifeng.