Original link: 7 lines JavaScript library for calling asynchronous functions
I was surprised by the popularity of a JavaScript template engine built on 20 lines of blog posts, and decided to use this article to introduce another small and practical tool I often use. We know that most of JavaScript operations in browsers are asynchronous, so we always need to use callback methods, And sometimes can't help but fall into the mud of callback and want to die
Suppose we have two functions, and we sequentially call one after execution. They all operate on the same variable. The first sets its value, and the second uses its value.
var value;
var A = function() {
setTimeout(function() {
value = 10;
}, 200);
}
var B = function() {
console.log(value);
}
Now, if we run a (); B (); we'll see that the output is undefined. This is because the a function uses the asynchronous method to set value. What we can do is to pass a callback function to a and let function a execute the callback function after execution.
var value;
var A = function(callback) {
setTimeout(function() {
value = 10;
callback();
}, 200);
};
var B = function() {
console.log(value);
};
A(function() {
B();
});
It's really useful, but imagine what happens when we add that we need to run five or more methods. Passing callbacks all the time can lead to confusing and very unsightly code. A good solution is to write a utility function, accept our program and control the whole process. Let's start with the simplest:
var queue = function(funcs) {
// 接下来请看,董卿???
}
Next, we need to run the function - queue ([a, b]) by passing a and B. We need to take the first function and execute it.
var queue = function(funcs) {
var f = funcs.shift();
f();
}
If you execute this code, you will see a typeerror: undefined is not a function. This is because the a function does not receive a callback parameter but tries to run it. Let's call a different method.
var queue = function(funcs) {
var next = function() {
// ...
};
var f = funcs.shift();
f(next);
};
The next method is called after a completes execution. It's a good idea to put the next step in the next function list. We can put code together, and we can pass the entire array (even if there are many functions in the array waiting to execute).
var queue = function(funcs) {
var next = function() {
var f = funcs.shift();
f(next);
};
next();
};
At this stage, we have basically reached our goal. That is, after function a is executed, it will call B to print out the correct value of the variable. The key here is the use of the shift method. It removes the first element of the array and returns it. Step by step, the FuncS array will become empty. So this could lead to another error. To prove this, let's assume that we still need to run these two functions, but we don't know their order. In this case, both functions should accept the callback parameter and execute it.
var A = function(callback) {
setTimeout(function() {
value = 10;
callback();
}, 200);
};
var B = function(callback) {
console.log(value);
callback();
};
Of course, we will get typeerror: undefined is not a function. To prevent this, we should check whether the functions array is empty.
var queue = function(funcs) {
(function next() {
if(funcs.length > 0) {
var f = funcs.shift();
f(next);
}
})();
};
All we have to do is define the next function and call it. This way of writing reduces the code a little. Let's try to imagine as many situations as possible. For example, the scope of the currently executed function. This keyword in the function may point to a global window object. , if we can set our own scope, of course, it's a cool thing.
var queue = function(funcs, scope) {
(function next() {
if(funcs.length > 0) {
var f = funcs.shift();
f.apply(scope, [next]);
}
})();
};
We add a parameter to the tiny class library. Then we use the apply function instead of calling f (next) directly to set the scope and pass in the parameter next. Same function, but much more beautiful. The last feature we need is the ability to pass parameters between functions. Of course, we don't know how many parameters will be used. That's why we need to use the arguments variable. As you may know, this variable is available in every JavaScript function and represents the parameters passed in. It's like an array, but not exactly. Because in the apply method, we need to use the real array and use a trick to transform.
var queue = function(funcs, scope) {
(function next() {
if(funcs.length > 0) {
var f = funcs.shift();
f.apply(scope, [next].concat(Array.prototype.slice.call(arguments, 0)));
}
})();
};
Here is the code for the test:
// 测试代码
var obj = {
value: null
};
queue([
function(callback) {
var self = this;
setTimeout(function() {
self.value = 10;
callback(20);
}, 200);
},
function(callback, add) {
console.log(this.value + add);
callback();
},
function() {
console.log(obj.value);
}
], obj);
The output after execution is:
30
10
In order to make the code readable and beautiful, we move some relevant codes to one line:
var queue = function(funcs, scope) {
(function next() {
if(funcs.length > 0) {
funcs.shift().apply(scope || {}, [next].concat(Array.prototype.slice.call(arguments, 0)));
}
})();
};
You can click here to view and debug the relevant code. The complete test code is as follows:
var queue = function(funcs, scope) {
(function next() {
if(funcs.length > 0) {
funcs.shift().apply(scope || {}, [next].concat(Array.prototype.slice.call(arguments, 0)));
}
})();
};
var obj = {
value: null
};
queue([
function(callback) {
var self = this;
setTimeout(function() {
self.value = 10;
callback(20);
}, 200);
},
function(callback, add) {
console.log(this.value + add);
callback();
},
function() {
console.log(obj.value);
}
], obj);