Contents
- Introduction to Asynchronous JavaScript
- Understanding Promises in JavaScript
- Creating and Using Promises
- Promise Chaining and Error Handling
- Introduction to the Fetch API
- Making API Requests with Fetch
- Handling Fetch Responses and Errors
- Real-World Examples and Best Practices
Introduction to Asynchronous JavaScript
As a modern web developer, it's crucial to stay ahead of the curve and constantly improve your skills. One essential skill you should have in your toolbox is the ability to work with asynchronous JavaScript. Embracing asynchronous techniques will empower you to create faster, more responsive, and user-friendly web applications.
Why Asynchronous JavaScript Matters
Traditionally, JavaScript executes code in a synchronous manner, meaning that one operation must finish before the next one can begin. However, in real-world scenarios, certain tasks such as fetching data from APIs, reading files, or running complex calculations can take an unpredictable amount of time to complete. With synchronous execution, this would block the rest of the code from running, causing a poor user experience and a slow, unresponsive application.
Asynchronous JavaScript tackles this problem by allowing you to execute time-consuming tasks without blocking the main thread, keeping your application smooth and responsive. By learning how to work with asynchronous JavaScript, you'll be able to create faster, more efficient applications that provide a seamless experience to your users.
Promises and Fetch: The Dynamic Duo
In this tutorial, we'll focus on two key components of asynchronous JavaScript: Promises and the Fetch API. Promises are a powerful and elegant way of working with asynchronous operations, allowing you to chain tasks together and handle errors with ease. The Fetch API is a modern, flexible, and feature-rich method for making HTTP requests, replacing the older XMLHttpRequest technique.
As you progress through this tutorial, you'll gain valuable knowledge and insights that will enhance your abilities as a web developer. We encourage you to follow along, practice the examples, and see the immediate impact that asynchronous JavaScript can have on your applications. So, let's dive in and explore the exciting world of Promises and the Fetch API!
Understanding Promises in JavaScript
As you venture into the world of asynchronous JavaScript, it's essential to grasp the concept of Promises. Promises are a powerful way to represent the eventual completion (or failure) of an asynchronous operation, making it easier to manage and reason about your code.
What is a Promise?
A Promise is a JavaScript object that represents the eventual result of an asynchronous operation. It acts as a placeholder for the value that will be returned when the operation is complete. Promises can be in one of three states:
- Pending: The initial state of a Promise; neither fulfilled nor rejected.
- Fulfilled: The operation completed successfully, and the Promise has a resulting value.
- Rejected: The operation failed, and the Promise has a reason for the failure.
Promises help you avoid callback hell, which is a situation where callbacks are nested within callbacks, making the code difficult to read and maintain. Instead, Promises allow you to chain asynchronous operations together in a more readable and organized manner.
Creating a Promise
To create a Promise, you use the Promise
constructor and pass in a single argument: a function called the "executor." The executor function takes two parameters: a resolve
function and a reject
function. You call the resolve
function when the asynchronous operation succeeds and pass the resulting value. If the operation fails, you call the reject
function and pass the error or reason for the failure.
Here's an example of creating a simple Promise:
const myPromise = new Promise((resolve, reject) => {
// Perform some asynchronous operation
});
Using Promises
Once you have a Promise, you can attach callbacks using the .then()
method for success and the .catch()
method for failures. You can also chain multiple .then()
and .catch()
methods together to handle the results of successive asynchronous operations.
Here's an example of using a Promise and handling its result:
myPromise
.then((result) => {
console.log('Success:', result);
})
.catch((error) => {
console.error('Error:', error);
});
Creating and Using Promises
Now that you have a solid understanding of what Promises are and why they are essential in asynchronous JavaScript, let's dive into creating and using them in more detail.
Creating a Custom Promise
To create a custom Promise, use the Promise
constructor and pass in the executor function. Within the executor function, you can perform any asynchronous operation, such as making an HTTP request, reading a file, or running a complex calculation. Remember to call the resolve
function when the operation is successful and the reject
function when it fails.
Here's an example of creating a custom Promise that simulates an asynchronous operation using setTimeout
:
const myAsyncOperation = (data) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (data) {
resolve(`Operation successful: ${data}`);
} else {
reject('Operation failed');
}
}, 1000);
});
};
Consuming a Promise
Once you've created a Promise, you can consume it by attaching success and failure callbacks using the .then()
and .catch()
methods. The .then()
method accepts a single argument: a callback function that will be called when the Promise is fulfilled. Similarly, the .catch()
method accepts a callback function that will be called when the Promise is rejected.
Here's how you can consume the myAsyncOperation
Promise:
myAsyncOperation('Hello, World!')
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
Handling Multiple Promises
In real-world scenarios, you might need to work with multiple asynchronous operations simultaneously. The Promise.all()
method allows you to wait for all Promises in an array to be fulfilled before executing the .then()
callback. If any Promise in the array is rejected, the .catch()
callback is executed immediately.
Here's an example of using Promise.all()
to handle multiple Promises:
const promise1 = myAsyncOperation('Operation 1');
const promise2 = myAsyncOperation('Operation 2');
const promise3 = myAsyncOperation('Operation 3');
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log('All operations succeeded:', results);
})
.catch((error) => {
console.error('One or more operations failed:', error);
});
With these techniques in hand, you can create, consume, and manage multiple Promises effectively. In the next section, we'll explore Promise chaining and error handling in more detail, helping you to further enhance your asynchronous JavaScript skills.
Promise Chaining and Error Handling
As your experience with Promises grows, you'll encounter situations where you need to execute multiple asynchronous operations in a specific order or handle errors more effectively. Promise chaining and advanced error handling techniques will be invaluable in these scenarios.
Chaining Promises
Promise chaining allows you to execute a series of asynchronous operations in sequence, where the output of one operation becomes the input of the next. You can chain Promises using the .then()
method, which returns a new Promise.
Here's an example of Promise chaining:
myAsyncOperation('Step 1')
.then((result) => {
console.log(result);
return myAsyncOperation('Step 2');
})
.then((result) => {
console.log(result);
return myAsyncOperation('Step 3');
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
In this example, each .then()
callback returns a new Promise, which allows you to chain them together. If any Promise in the chain is rejected, the error will be caught by the .catch()
callback at the end.
Error Handling
In Promise chains, it's essential to handle errors effectively. When an error occurs, it's crucial to provide clear and actionable feedback to the user or log the issue for further investigation.
The .catch()
method is used to handle errors in Promise chains. It's important to place the .catch()
callback at the end of the chain, as this ensures that it will catch any errors that occur in any of the preceding .then()
callbacks.
Here's an example of error handling in a Promise chain:
myAsyncOperation('Step 1')
.then((result) => {
console.log(result);
return myAsyncOperation(null); // This will cause an error
})
.then((result) => {
console.log(result);
return myAsyncOperation('Step 3');
})
.catch((error) => {
console.error('An error occurred:', error);
});
In this example, the second .then()
callback will cause an error, which will be caught by the .catch()
callback at the end of the chain.
By mastering Promise chaining and error handling, you'll be able to create complex and robust asynchronous workflows that are easy to read and maintain. In the next section, we'll introduce the Fetch API, a powerful and flexible tool for making HTTP requests in JavaScript.
Introduction to the Fetch API
With a solid understanding of Promises, you're now ready to explore the Fetch API, a modern and versatile tool for making HTTP requests in JavaScript. The Fetch API is built on top of Promises, making it a perfect fit for your asynchronous coding toolkit.
What is the Fetch API?
The Fetch API is a built-in JavaScript feature that provides a simple and powerful way to make HTTP requests, including GET, POST, PUT, DELETE, and other methods. It is designed to replace the older XMLHttpRequest technique, offering better performance, cleaner syntax, and additional features.
One of the main advantages of the Fetch API is that it returns a Promise, allowing you to easily handle the response and errors using the techniques you've already learned.
Basic Fetch Request
To make an HTTP request using the Fetch API, you simply call the fetch()
function and pass in the URL of the resource you want to fetch. By default, the fetch()
function makes a GET request, but you can customize this behavior using options.
Here's an example of a basic fetch request:
fetch('https://api.example.com/data')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log('Fetched data:', data);
})
.catch((error) => {
console.error('Error fetching data:', error);
});
In this example, the fetch()
function returns a Promise that resolves with a Response
object. You can then use the .json()
method to parse the JSON data in the response, which also returns a Promise.
In the next section, we'll go deeper into making API requests with Fetch, handling different types of responses, and managing errors effectively. As you become more proficient with the Fetch API, you'll be able to create powerful and efficient web applications that interact seamlessly with APIs and remote resources.
Making API Requests with Fetch
Now that you have an introduction to the Fetch API, let's dive deeper into making various types of API requests, handling different response formats, and managing errors effectively.
Customizing Fetch Requests
The Fetch API allows you to customize your HTTP requests by passing an optional options
object as the second argument to the fetch()
function. This object can include headers, method, body, and other configuration options.
Here's an example of a POST request with custom headers and a JSON payload:
const url = 'https://api.example.com/data';
const data = {
key: 'value',
};
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
},
body: JSON.stringify(data),
};
fetch(url, options)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log('POST response:', data);
})
.catch((error) => {
console.error('Error posting data:', error);
});
In this example, the options
object specifies the method as 'POST', sets custom headers, and provides a JSON payload in the body.
Handling Different Response Formats
The Fetch API supports various response formats, including JSON, text, Blob, and FormData. Depending on the API or resource you're working with, you may need to handle different types of responses.
Here's an example of handling a text response:
fetch('https://api.example.com/text')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.text();
})
.then((text) => {
console.log('Fetched text:', text);
})
.catch((error) => {
console.error('Error fetching text:', error);
});
In this example, the .text()
method is used instead of .json()
to parse the response as plain text.
Advanced Error Handling
When using the Fetch API, it's crucial to handle errors effectively. In addition to catching network errors, you should also check the ok
property of the Response
object to determine if the HTTP request was successful.
Here's an example of advanced error handling with Fetch:
fetch('https://api.example.com/data')
.then((response) => {
if (!response.ok) {
return response.json().then((errorData) => {
const error = new Error('HTTP error');
error.status = response.status;
error.data = errorData;
throw error;
});
}
return response.json();
})
.then((data) => {
console.log('Fetched data:', data);
})
.catch((error) => {
console.error(`Error fetching data (status: ${error.status}):`, error.data);
});
In this example, if the HTTP request is unsuccessful, the error data is extracted from the JSON response and included in the thrown error object.
By mastering the Fetch API, you'll be able to create powerful and efficient web applications that interact seamlessly with APIs and remote resources. In the next section, we'll discuss handling Fetch responses and errors in more detail, along with best practices and real-world examples.
Handling Fetch Responses and Errors: Best Practices
As you work with the Fetch API, it's crucial to handle responses and errors effectively to ensure a smooth user experience and to maintain the robustness of your application. In this section, we'll discuss some best practices for handling Fetch responses and errors, along with real-world examples.
Checking Response Status
When using the Fetch API, it's essential to check the ok
property of the Response
object to determine if the HTTP request was successful. If the ok
property is false
, you should handle the error appropriately, such as displaying an error message or logging the issue.
Here's an example of checking the response status and handling errors:
fetch('https://api.example.com/data')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log('Fetched data:', data);
})
.catch((error) => {
console.error('Error fetching data:', error);
// Display error message or log the issue as needed
});
Handling Network Errors
In addition to handling HTTP errors, it's important to catch network errors that can occur when using the Fetch API. Network errors can be caused by various factors, such as a loss of internet connectivity or a server outage.
You can catch network errors using the .catch()
method in your Promise chain. When a network error occurs, it's essential to provide clear and actionable feedback to the user or log the issue for further investigation.
Here's an example of handling network errors with Fetch:
fetch('https://api.example.com/data')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log('Fetched data:', data);
})
.catch((error) => {
console.error('Error fetching data:', error);
// Handle network errors, display an error message or log the issue as needed
});
Creating a Fetch Wrapper Function
A useful best practice when working with the Fetch API is to create a wrapper function that handles common tasks, such as checking the response status, parsing the response, and handling errors. This can help you to simplify your code and ensure consistent error handling across your application.
Here's an example of a Fetch wrapper function:
async function fetchData(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json();
const error = new Error(`HTTP error: ${response.status}`);
error.data = errorData;
throw error;
}
return await response.json();
} catch (error) {
console.error('Error fetching data:', error);
// Handle errors, display an error message or log the issue as needed
throw error;
}
}
// Example usage:
fetchData('https://api.example.com/data')
.then((data) => {
console.log('Fetched data:', data);
})
.catch((error) => {
console.error('Error fetching data:', error);
});
By following these best practices for handling Fetch responses and errors, you'll be able to create robust and reliable web applications that provide a seamless experience for your users. Keep refining your skills with the Fetch API and Promises, and you'll become an expert in asynchronous JavaScript in no time.
Real-World Examples and Best Practices
Throughout this tutorial, you've learned about asynchronous JavaScript, Promises, the Fetch API, and handling responses and errors. In this section, we'll explore some real-world examples and best practices that you can apply to your projects, helping you to create efficient and maintainable applications.
Fetch with Request and Response Objects
When working with more complex requests or responses, you can use the Request
and Response
objects provided by the Fetch API. These objects offer more flexibility and control, allowing you to easily create, modify, and inspect requests and responses.
Here's an example of using a Request
object with the Fetch API:
const url = 'https://api.example.com/data';
const myRequest = new Request(url, {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json',
}),
body: JSON.stringify({ key: 'value' }),
});
fetch(myRequest)
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error('Error:', error));
In this example, a Request
object is created and passed to the fetch()
function, allowing you to specify the request method, headers, and body more explicitly.
Using Async/Await with Fetch
To further improve the readability and maintainability of your asynchronous code, you can use the async/await
syntax in combination with the Fetch API. This approach makes your code look more synchronous, allowing you to write cleaner and more intuitive asynchronous workflows.
Here's an example of using async/await
with the Fetch API:
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
console.log('Fetched data:', data);
} catch (error) {
console.error('Error fetching data:', error);
}
}
fetchData('https://api.example.com/data');
In this example, the fetchData()
function is marked as async
, allowing you to use the await
keyword to wait for the Promises returned by the fetch()
function and the .json()
method.
Caching Fetch Responses
When working with APIs and remote resources, it's essential to optimize performance and reduce network overhead by caching responses when appropriate. You can use the Cache
API to store and retrieve Fetch responses, reducing latency and saving bandwidth.
Here's an example of caching a Fetch response using the Cache
API:
const url = 'https://api.example.com/data';
async function fetchDataWithCache(url) {
const cache = await caches.open('my-cache');
const cachedResponse = await cache.match(url);
if (cachedResponse) {
console.log('Returning cached data:', cachedResponse);
return cachedResponse.json();
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
// Cache the response for future use
cache.put(url, response.clone());
return response.json();
}
fetchDataWithCache(url)
.then((data) => console.log('Fetched data:', data))
.catch((error) => console.error('Error fetching data:', error));
In this example, the fetchDataWithCache()
function first checks if there's a cached response for the requested URL. If so, it returns the cached response. If not, it performs a Fetch request, caches the response, and returns the data.
By applying these real-world examples and best practices in your projects, you'll create efficient and maintainable web applications that provide a smooth user experience. Asynchronous JavaScript, Promises, and the Fetch API are powerful tools that will continue to serve you well as you develop increasingly complex applications and interact with a wide range of APIs and remote resources.
Throttling and Debouncing Fetch Requests
When working with the Fetch API, it's essential to manage the frequency of your requests to avoid overloading servers or exceeding API rate limits. Throttling and debouncing are two techniques that can help you control the rate of your Fetch requests.
Throttling limits the execution of a function to a specified rate, ensuring that it does not execute more frequently than allowed. Debouncing, on the other hand, delays the execution of a function until a certain amount of time has passed since the last invocation, reducing the number of unnecessary requests.
Here's an example of a debounced Fetch function:
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
const debouncedFetch = debounce(async (url) => {
try {
const response = await fetch(url);
const data = await response.json();
console.log('Fetched data:', data);
} catch (error) {
console.error('Error fetching data:', error);
}
}, 300);
debouncedFetch('https://api.example.com/data');
In this example, the debounce()
function is used to create a debounced version of the Fetch function, which delays the execution of the Fetch request until 300 milliseconds have passed since the last invocation.
By using throttling and debouncing techniques, you can optimize the performance of your web applications, reduce server load, and ensure that you stay within API rate limits.
Error Handling and Retry Mechanisms
In a real-world scenario, it's crucial to handle errors effectively and implement retry mechanisms for transient failures. Retry mechanisms can help your application recover gracefully from temporary issues, such as network errors or server timeouts.
Here's an example of a Fetch function with a retry mechanism:
async function fetchWithRetry(url, options = {}, retries = 3, backoff = 500) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return await response.json();
} catch (error) {
if (retries > 0) {
console.log(`Retrying fetch (${retries} retries remaining)...`);
await new Promise((resolve) => setTimeout(resolve, backoff));
return fetchWithRetry(url, options, retries - 1, backoff * 2);
}
console.error('Error fetching data:', error);
throw error;
}
}
fetchWithRetry('https://api.example.com/data')
.then((data) => console.log('Fetched data:', data))
.catch((error) => console.error('Error fetching data:', error));
In this example, the fetchWithRetry()
function takes additional parameters for the number of retries and the backoff time. If an error occurs, the function waits for the specified backoff time, then retries the Fetch request. The backoff time doubles with each retry attempt, ensuring that the retries are spaced out appropriately.
By implementing error handling and retry mechanisms, you can create resilient web applications that handle transient failures gracefully and provide a reliable user experience.
In conclusion, mastering asynchronous JavaScript, Promises, and the Fetch API is crucial for creating efficient, responsive, and maintainable web applications. Throughout this tutorial, we've covered the fundamentals of asynchronous programming, the basics of Promises and the Fetch API, handling responses and errors, and explored real-world examples and best practices.
By applying the concepts and techniques discussed in this tutorial, you'll be well-equipped to develop web applications that interact seamlessly with APIs and remote resources, provide a smooth user experience, and recover gracefully from errors and transient failures.
As you continue to refine your skills and explore the vast possibilities of asynchronous JavaScript, always remember the importance of clean, maintainable code, and effective error handling. Embrace the power of Promises, the Fetch API, and other asynchronous tools to build reliable, high-performance web applications that will stand the test of time.
Related tutorials
JavaScript Tutorial for Beginners and Advanced
JavaScript 101: Mastering Variables & Functions
JavaScript: Learn Strings, Arrays, OOP & Asynchronous Coding
DOM Magic: HTML Interaction with JavaScript
Advanced JavaScript: Closures, Prototypes & OOP
Learn Async JavaScript: Promises & Fetch online learning
Javascript Promises
Download free course Javascript Promises, book to learn how to use promises in Javascript, and why you should use it, PDF ebook by Samy Pessé.
Professional Node.JS development
Download tutorial Professional Node.JS development, free PDF course by Tal Avissar on 60 pages.
Computer Fundamentals
Learn the basics of computers with our free PDF ebook tutorial on Computer Fundamentals. Perfect for beginners, it covers assembly-level programming, the fetch-execute cycle and more. Start your learning journey today!
Essential Javascript
Download free Essential Javascript course material and training written by Patrick Hunlock (PDF file 22 pages)
JavaScript for beginners
Learn JavaScript from scratch with our comprehensive PDF ebook tutorial, designed for beginners. Download to start building interactive web pages with clear explanations and practical examples.
JavaScript course
Download free JavaScript course material and training written by Osmar R. Zaïane University of Alberta (PDF file)
TypeScript Deep Dive
Download TypeScript Deep Dive ebook, a JavaScript compiler, free PDF course tutorial on 368 pages.
JavaScript: A Crash Course
Download free JavaScript a crash course material and training Core Language Syntax for web programming (PDF file 28 pages)
JavaScript for impatient programmers
Download free book JavaScript for impatient programmers, course tutorial on 165 pages by Dr. Axel Rauschmayer
JavaScript Basics
JavaScript Basics PDF ebook tutorial: Comprehensive guide for beginners to learn the fundamentals of JavaScript. Free to download with interactive examples.
Introduction to jQuery
Download free Introduction to jQuery, javascript course material and training, PDF file by Girl Develop It
Learning JavaScript
Download free ebook Learning JavaScript for web development, PDF course and tutorials written by Stack Overflow Documentation.
HTML, CSS, Bootstrap, Javascript and jQuery
Download free tutorial HTML, CSS, Bootstrap, Javascript and jQuery, pdf course in 72 pages by Meher Krishna Patel.
JavaScript Notes for Professionals book
Download free ebook JavaScript Notes for Professionals book, PDF course compiled from Stack Overflow Documentation on 490 pages.
JavaScript Front-End Web App Tutorial Part 4
Learn how to manage unidirectional associations between object types, such as the associations assigning publishers and authors to books, PDF file by Gerd Wagner.
JavaScript Front-End Web App Tutorial Part 5
Learn how to manage bidirectional associations between object types, such as between books and their authors, and authors and their books, PDF file by Gerd Wagner.
JS Functions, Objects, and Arrays
Download free JavaScript Functions, Objects, and Arrays course material and training written by Charles Severance (PDF file 32 pages)
React In-depth
Download eBook on developing production applications with React, a JavaScript library for building user interfaces, free course on 70 pages.
A MySQL Tutorial for beginners
Download free Course A MySQL Tutorial for beginners training, PDF file by tizag.com on 58 pages.
Core JavaScript Documentation
Download free tutorial Core JavaScript Documentation with examples and exercises, PDF course on 36 pages.
JavaScript Front-End Web App Tutorial Part 1
Learn how to build a front-end web application with minimal effort, using plain JavaScript and the LocalStorage API, PDF file by Gerd Wagner.
jQuery Fundamentals
Download course JavaScript jQuery Fundamentals, free PDF tutorial by Rebecca Murphey.
JavaScript Front-End Web App Tutorial Part 3
Learn how to build a front-end web application with enumeration attributes, using plain JavaScript, PDF file by Gerd Wagner.
Javascript Essentials
Download free Javascript Essentials course material, tutorial training, a PDF file by Javascript Essentials on 23 pages.
JavaScript Front-End Web App Tutorial Part 6
An advanced tutorial about developing front-end web applications with class hierarchies, using plain JavaScript, PDF file by Gerd Wagner.
JavaScript Front-End Web App Tutorial Part 2
Learn how to build a front-end web application with responsive constraint validation using plain JavaScript, PDF file by Gerd Wagner .
Getting Started with AngularJS
Download free presentation to Getting Started with AngularJS, javascript framwork made by google, PDF file by Jeremy Zerr.
Heroku & Node.js
Download free course To Learn how to build and deploy applications with Heroku and Node.js. PDF tutorial by Samy Pessé.
Front-end Developer Handbook 2018
Download Front-end Developer Handbook 2018 ebook, client-side development with client-side development is the practice of producing HTML, CSS and JavaScript. free PDF on 168 pages.
D3js Tutorial
Download free D3js Tutorial course material, tutorial training, a PDF file by Tom Torsney-Weir Michael Trosin.
All right reserved 2011-2024 copyright © computer-pdf.com v5 +1-620-355-1835 - Courses, corrected exercises, tutorials and practical work in IT.
Partner sites PDF Manuales (Spanish) | Cours PDF (French)