patternjavascriptMinor
Callback functions to select the tab to the left in Google Chrome
Viewed 0 times
leftthegoogletabselectcallbackchromefunctions
Problem
I'm a JavaScript rookie, but I wrote a JavaScript function as part of a Chrome extension that will activate the tab to the left of the currently active tab. The function is pretty ugly, but I couldn't figure out another way to write it.
The logic is pretty simple, but I ran into problems setting variables throughout the various scopes. How would I move the functionality of the Chrome API calls into individual functions that return values? Can callback functions return a value?
The logic is pretty simple, but I ran into problems setting variables throughout the various scopes. How would I move the functionality of the Chrome API calls into individual functions that return values? Can callback functions return a value?
// Activates the tab to the left of the currently active tab
tabLeft = function() {
var parent = this;
var activeTabIndex;
var targetTabIndex;
// Get the index of the active tab
chrome.tabs.query({'active':true, 'currentWindow': true}, function(tabs) {
parent.activeTabIndex = tabs[0].index;
});
// Get the count of the number of tabs in the current window
chrome.tabs.query({'currentWindow': true}, function(tabs) {
var tabCount = tabs.length;
parent.targetTabIndex = (parent.activeTabIndex == 0) ? (tabCount - 1) : (parent.activeTabIndex - 1);
// Get the tabId of the of the tab we want to activate
chrome.tabs.query({'index': parent.targetTabIndex}, function(tab) {
var tabId = tab[0].id;
// Activate the target tab
chrome.tabs.update(tabId, {'active': true}, function() {});
});
});
};Solution
Some of the Chrome APIs are implemented using callbacks. This means that these methods are potentially async. Code doesn't run in the order they're written, you have been warned.
The closest you'll get to sequentially written async code is by using Promises. In simple terms, these are objects that hold some state, and when a certain state is attained (resolve or reject), the attached handlers get executed.
The beauty lies in
I think Chrome should have support for Promises already. Here's an example where a we wrap the
For now, it still looks like a jumble. However, you can now pull out functions and just hand references to
Breaking down an operation into promises has no fixed rule. However, I write down the code in the sequence I want (just a flat list of things to do). Then run though them again. This time, if you see an async operation, that ends a
The closest you'll get to sequentially written async code is by using Promises. In simple terms, these are objects that hold some state, and when a certain state is attained (resolve or reject), the attached handlers get executed.
The beauty lies in
then which when returned a promise, attaches the next chained then to that promise instead. This makes the next then appear to wait for the previous async operation. However, when returned a non-promise value, it uses that value as the resolved value for the next chained then. The ability make the code appear to wait, the ability to alter the resolved values as well as carry values through makes Promises powerful.I think Chrome should have support for Promises already. Here's an example where a we wrap the
chrome.tabs.query in a promise, and when it succeeds, it resolves the promise.function promiseQuery(options){
return new Promise(function(resolve,reject){
chrome.tabs.query(options, resolve);
});
}
function promiseUpdate(tabId,options){
return new Promise(function(resolve,reject){
chrome.tabs.update(tabId,options,resolve);
});
}
function tabLeft(){
promiseQuery({'active':true, 'currentWindow': true})
.then(function(tabs){
// This function executes when the promiseQuery resolves
// Get the current index
var activeIndex = tabs[0].index;
// We fire another query. But instead of returning a promise, we can chain a then
// to alter the resolved value.
return promiseQuery({'currentWindow': true})
.then(function(tabs){
return{
length : tabs.length,
activeIndex : activeIndex
}
});
})
.then(function(data){
// This executes with data being the altered resolved value
var targetIndex = ((data.activeIndex === 0) ? data.length : data.activeIndex) - 1;
return promiseQuery({'index': targetIndex});
})
.then(function(tabs){
// same here
var tabId = tab[0].id;
// As long as you return a promise
return promiseUpdate(tabId, {'active': true});
})
.then(function(){
// Done
});
}For now, it still looks like a jumble. However, you can now pull out functions and just hand references to
then. There's no more problem with persisting values in an outer scope since you can just pass them through the then chain.function promiseQuery(options){
return new Promise(function(resolve,reject){
chrome.tabs.query(options, resolve);
});
}
function promiseUpdate(tabId,options){
return new Promise(function(resolve,reject){
chrome.tabs.update(tabId,options,resolve);
});
}
function initialCall(){
return promiseQuery({'active':true, 'currentWindow': true})
}
function activeAndLengthQueries(){
var activeIndex = tabs[0].index;
return promiseQuery({'currentWindow': true})
.then(function(tabs){
return{
length : tabs.length,
activeIndex : activeIndex
}
});
}
function targetTabQuery(){
var targetIndex = ((data.activeIndex === 0) ? data.length : data.activeIndex) - 1;
return promiseQuery({'index': targetIndex});
}
function tabUpdateFunction(){
var tabId = tab[0].id;
return promiseUpdate(tabId, {'active': true});
}
function doneFunction(){
}
function tabLeft(){
initialCall()
.then(activeAndLengthQueries)
.then(targetTabQuery)
.then(tabUpdateFunction)
.then(doneFunction);
}Breaking down an operation into promises has no fixed rule. However, I write down the code in the sequence I want (just a flat list of things to do). Then run though them again. This time, if you see an async operation, that ends a
then (notice how the previous example ends with return somePromiseGeneratingFunction). Do that operation and return it's promise. The sequence will continue in the next then.Code Snippets
function promiseQuery(options){
return new Promise(function(resolve,reject){
chrome.tabs.query(options, resolve);
});
}
function promiseUpdate(tabId,options){
return new Promise(function(resolve,reject){
chrome.tabs.update(tabId,options,resolve);
});
}
function tabLeft(){
promiseQuery({'active':true, 'currentWindow': true})
.then(function(tabs){
// This function executes when the promiseQuery resolves
// Get the current index
var activeIndex = tabs[0].index;
// We fire another query. But instead of returning a promise, we can chain a then
// to alter the resolved value.
return promiseQuery({'currentWindow': true})
.then(function(tabs){
return{
length : tabs.length,
activeIndex : activeIndex
}
});
})
.then(function(data){
// This executes with data being the altered resolved value
var targetIndex = ((data.activeIndex === 0) ? data.length : data.activeIndex) - 1;
return promiseQuery({'index': targetIndex});
})
.then(function(tabs){
// same here
var tabId = tab[0].id;
// As long as you return a promise
return promiseUpdate(tabId, {'active': true});
})
.then(function(){
// Done
});
}function promiseQuery(options){
return new Promise(function(resolve,reject){
chrome.tabs.query(options, resolve);
});
}
function promiseUpdate(tabId,options){
return new Promise(function(resolve,reject){
chrome.tabs.update(tabId,options,resolve);
});
}
function initialCall(){
return promiseQuery({'active':true, 'currentWindow': true})
}
function activeAndLengthQueries(){
var activeIndex = tabs[0].index;
return promiseQuery({'currentWindow': true})
.then(function(tabs){
return{
length : tabs.length,
activeIndex : activeIndex
}
});
}
function targetTabQuery(){
var targetIndex = ((data.activeIndex === 0) ? data.length : data.activeIndex) - 1;
return promiseQuery({'index': targetIndex});
}
function tabUpdateFunction(){
var tabId = tab[0].id;
return promiseUpdate(tabId, {'active': true});
}
function doneFunction(){
}
function tabLeft(){
initialCall()
.then(activeAndLengthQueries)
.then(targetTabQuery)
.then(tabUpdateFunction)
.then(doneFunction);
}Context
StackExchange Code Review Q#70022, answer score: 2
Revisions (0)
No revisions yet.