jQuery Promise. How to make promises work for you

I just wanted to pull the data from the site for authorization and what happened.

file_get_contents

file_get_contents - with its streams, as it turns out, the method is shameful and many servers can stupidly easily block such a request to pages. For a simple download of verified sites - the method is simply indispensable. Very simple and convenient.

Here is a one-pager that can load data with a POST request:

$result = file_get_contents($url, false, stream_context_create(array('http' => array('method' => 'POST','header' => 'Content-Type: application/xml','content' => $xml))));

formatted version:

pear:http_request

require_once "HTTP/Request.php";
$req =& new HTTP_Request("http://www.php.net");
$req->setMethod(HTTP_REQUEST_METHOD_POST);
$req->addPostData("Foo", "bar");
if (!PEAR::isError($req->sendRequest())) (
$response1 = $req->getResponseBody();
) else (
$response1 = "";
}
$req->setMethod(HTTP_REQUEST_METHOD_GET);
$req->setURL("http://pear.php.net");
$req->clearPostData();
if (!PEAR::isError($req->sendRequest())) (
$response2 = $req->getResponseBody();
) else (
$response2 = "";
}
echo $response1;
echo $response2;
?>

The Uroztsky method just infuriates looking into such a bucket. Of course, I could comb this code taken from here: http://pear.php.net/manual/pl/package.http.http-request.intro.php

But it looks like it works, but it looks like crap.

Pecl_Http

What kind of crap is this - some strange PHP extension, for which I could not find examples, but on php.net it's a GIANT MANUAL.

However, I found something to look at, I hope you yourself will give more examples:

$request = Request::factory($url); $request->method("POST"); $request->headers($header); $request->post($post_params); $request->execute();

curl

The most global method, which is used in 90% of cases, but they say that it does not always work.

if($curl = curl_init()) (

curl_setopt($curl, CURLOPT_URL, 'http://mysite.ru/receiver.php');

curl_setopt($curl, CURLOPT_RETURNTRANSFER,true);

curl_setopt($curl, CURLOPT_POST, true);

curl_setopt($curl, CURLOPT_POSTFIELDS, "a=4&b=7");

$out = curl_exec($curl);

curl_close($curl);

The CURL code is as ugly as my ass no matter what anyone says.

sockets

I'll give you an example right away:

$fp = fsockopen($url, 80, $errno, $errstr, 30);

echo "ERROR: $errstr ($errno)
\n";

$error="ERROR: $errstr ($errno)";

$out = "GET / HTTP/1.1\r\n";

$out .="User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1284.0 Safari/537.13\r\n";
$out .= "Host: $url\r\n";

$out .= "Referer: http://site";

$out .= "Connection: Close\r\n\r\n";

$out .= "\r\n";
fwrite($fp, $out);

while (!feof($fp)) (

$src.=fgets($fp, 128);

This is an example from life, Uglier nowhere, but it works everywhere and always, no libraries are needed, just tears in my eyes. Even PHP4 is enough.

Snoopy

They say something cooler than Snoopy. I did not check it, I looked at the code, there is no documentation. Interesting but time consuming.

Zend_http_client/Zend:http

Documentation is even in Russian something. Big, powerful, boring.

Buzz – Simple HTTP Request Library

Buzz is another library for HTTP requests. Here is an example code:

1 $request = new Buzz\Message\Request("HEAD" , "/" , "http://google.com");
2 $response = new Buzz\Message\Response();
3
4 $client = new Buzz\Client\FileGetContents();
5 $client ->send($request , $response);
6
7 echo $request ;
8 echo $response ;

Haven't looked yet and haven't figured it out, but you should definitely take a look.

Requests – Easy HTTP Requests

The Requests library will make it easy to make HTTP requests. If you (like me) can't remember the Curl syntax in any way, then this library is for you, sample code.

In PHP, the predefined $_POST variable is used to form from the method = "post" in the value of the collection.

$_POST Variable

Forms predefined $_POST variable is used to collect from the method = "post" in value.

Information form sent with the POST method from, for anyone not visible (not displayed in the browser"s address bar), and on the amount of information sent is also not limited.

Note: However, by default, the maximum amount of information sent to the POST method is 8 MB (can be changed by setting the php.ini file post_max_size).

Examples

form.html file code is as follows:

本教程(website)

名字: 年龄:

When the user clicks the "Submit" button, the URL is similar to the following:

http://www..php

"Welcome.php" file can now collect $_POST variable to the form data (Note that the name of the form fields will automatically become $_POST array keys):

欢迎 !
你的年龄是 岁。

Demo accessed through a browser as follows:

When to use method = "post"?

Information from a form with the POST method of transmission, are not visible to anyone, and on the amount of information sent is also not limited.

However, since the tag is not displayed in the URL, it is not possible to bookmark this page.

PHP $_REQUEST variable

Predefined $_REQUEST variable contains the $_GET, $_POST and content of the $_COOKIE.

$_REQUEST Variable can be used to collect form data sent via GET and POST methods.

Examples

You can "welcome.php" file is modified as follows the code, it can accept $_GET, $_POST and other data.

欢迎 !
你的年龄是 岁。

Many people start writing a project to work with a single task, without implying that it can grow into a multi-user management system, let's say, content or, God forbid, production. And everything seems to be great and cool, everything works until you start to understand that the code that is written consists entirely of crutches and hardcode. The code is mixed with layout, requests and crutches, sometimes even unreadable. An urgent problem arises: when adding new features, you have to fiddle with this code for a very long time, remembering “what was written there?” and curse yourself in the past.

You may have even heard about design patterns and even flipped through these great books:

  • E. Gamma, R. Helm, R. Johnson, J. Vlissides “Object Oriented Design Techniques. Design patterns";
  • M. Fowler "Architecture of Enterprise Software Applications".
And many, not afraid of huge manuals and documentation, tried to study any of the modern frameworks and, faced with the difficulty of understanding (due to the presence of many architectural concepts cleverly linked to each other), they put off the study and application of modern tools on the back burner.

The presented article will be useful primarily for beginners. In any case, I hope that in a couple of hours you will be able to get an idea of ​​​​the implementation of the MVC pattern that underlies all modern web frameworks, as well as get “food” for further reflection on “how to do it”. At the end of the article, a selection of useful links is provided that will also help you understand what web frameworks (besides MVC) consist of and how they work.

Hardened PHP programmers are unlikely to find something new for themselves in this article, but their comments and comments on the main text would be very helpful! Because without theory, practice is impossible, and without practice, theory is useless, then at first there will be a little bit of theory, and then we will move on to practice. If you are already familiar with the concept of MVC, you can skip the theory section and jump straight into practice.

1. Theory

The MVC pattern describes a simple way to build an application structure that aims to separate the business logic from the user interface. As a result, the application is easier to scale, test, maintain, and of course implement.

Consider the conceptual scheme of the MVC template (in my opinion, this is the most successful scheme of those that I have seen):

In an MVC architecture, the model provides the data and business logic rules, the view is responsible for the user interface, and the controller provides the interaction between the model and the view.

A typical MVC application workflow can be described as follows:

  1. When a user enters a web resource, the initialization script creates an instance of the application and launches it for execution.
    This displays a view of, say, the main page of the site.
  2. The application receives a request from the user and determines the requested controller and action. In the case of the main page, the default action is performed ( index).
  3. The application instantiates the controller and runs the action method,
    which, for example, contains calls to the model that read information from the database.
  4. After that, the action generates a view with the data received from the model and displays the result to the user.
Model- contains the business logic of the application and includes methods for sampling (these can be ORM methods), processing (for example, validation rules) and providing specific data, which often makes it very thick, which is quite normal.
The model should not directly interact with the user. All variables related to the user's request must be processed in the controller.
The model should not generate HTML or other rendering code, which may change depending on the needs of the user. Such code should be handled in views.
The same model, for example: the user authentication model can be used both in the user and in the administrative part of the application. In this case, you can move the common code into a separate class and inherit from it, defining methods specific to subapplications in the heirs.

View- used to set the external display of data received from the controller and model.
Views contain HTML markup and small inserts of PHP code for crawling, formatting, and displaying data.
Should not access the database directly. Models should do this.
Should not work with data received from a user request. This task must be performed by the controller.
It can directly access the properties and methods of the controller or models to get output-ready data.
Views are usually divided into a common template containing markup common to all pages (for example, a header and footer) and template parts that are used to display data output from the model or display data entry forms.

Controller- a link that connects models, views and other components into a working application. The controller is responsible for handling user requests. The controller must not contain SQL queries. It is better to keep them in models. The controller must not contain HTML or other markup. It deserves to be brought out into the open.
In a well-designed MVC application, controllers are usually very thin and contain only a few dozen lines of code. What can not be said about Stupid Fat Controllers (SFC) in CMS Joomla. The logic of the controller is quite typical and most of it is taken out in the base classes.
Models, on the contrary, are very thick and contain most of the code associated with data processing, since the structure of the data and the business logic it contains are usually quite specific to a particular application.

1.1. Front Controller and Page Controller

In most cases, user interaction with a web application takes place through links. Look now at the address bar of the browser - you received this text from this link. Other links, such as those on the right side of this page, will take you to other content. Thus, the link represents a specific command to the web application.

I hope you have already noticed that different sites can have completely different formats for constructing the address bar. Each format can represent the architecture of a web application. Although this is not always the case, in most cases it is a clear fact.

Consider two options for the address bar, which show some text and a user profile.

Approximate processing code in this case:
switch($_GET["action"]) ( case "about" : require_once("about.php"); // "About Us" page break; case "contacts" : require_once("contacts.php"); // page "Contacts" break; case "feedback" : require_once("feedback.php"); // page "Feedback" break; default: require_once("page404.php"); // page "404" break; )
I think almost everyone has done this before.

Using the URL routing engine, you can configure your application to accept requests like this to display the same information:
http://www.example.com/contacts/feedback

Here, contacts is a controller, and feedback is a method of the contacts controller that renders a feedback form, and so on. We will return to this issue in the practical part.

It's also worth knowing that the routers of many web frameworks allow you to create arbitrary URL routes (specify what each part of the URL means) and rules for processing them.
Now we have sufficient theoretical knowledge to move on to practice.

2. Practice

First, let's create the following file and folder structure:


Looking ahead, I will say that the base classes Model, View and Controller will be stored in the core folder.
Their children will be stored in the controllers, models and views directories. File index.php this is the entry point to the application. File bootstrap.php initiates the download of the application, including all the necessary modules, etc.

Let's go sequentially; open the index.php file and fill it with the following code:
ini_set("display_errors", 1); require_once "application/bootstrap.php";
There should be no questions here.

Next, let's go straight to the fall bootstrap.php:
require_once "core/model.php"; require_once "core/view.php"; require_once "core/controller.php"; require_once "core/route.php"; Route::start(); // start the router
The first three lines will include currently non-existent kernel files. The last lines include the file with the router class and start it for execution by calling the static start method.

2.1. Implementing a URL Router

For now, let's deviate from the implementation of the MVC pattern and focus on routing. The first step we need to take is to write the following code into .htaccess:
RewriteEngine On RewriteCond %(REQUEST_FILENAME) !-f RewriteCond %(REQUEST_FILENAME) !-d RewriteRule .* index.php [L]
This code will redirect processing of all pages to index.php, which is what we need. Remember in the first part we talked about the Front Controller?!

We will put the routing in a separate file route.php to the core directory. In this file, we will describe the Route class, which will run the methods of the controllers, which in turn will generate the appearance of the pages.

Route.php file content

class Route ( static function start() ( // default controller and action $controller_name = "Main"; $action_name = "index"; $routes = explode("/", $_SERVER["REQUEST_URI"]); // get controller name if (!empty($routes)) ( $controller_name = $routes; ) // get action name if (!empty($routes)) ( $action_name = $routes; ) // add prefixes $model_name = " Model_".$controller_name; $controller_name = "Controller_".$controller_name; $action_name = "action_".$action_name; // hook the file with the model class (there may not be a model file) $model_file = strtolower($model_name). ".php"; $model_path = "application/models/".$model_file; if(file_exists($model_path)) ( include "application/models/".$model_file; ) // hook the controller class file $controller_file = strtolower ($controller_name).".php"; $controller_path = "application/controllers/".$controller_file; if(file_exists($controller_path)) ( include "application/controllers/".$controller_f ile; ) else ( /* it would be correct to throw an exception here, but for simplicity we will immediately redirect to the 404 page */ Route::ErrorPage404(); ) // create a controller $controller = new $controller_name; $action = $action_name; if(method_exists($controller, $action)) ( // call the controller action $controller->$action(); ) else ( // it would also make more sense to throw an exception here Route::ErrorPage404(); ) ) function ErrorPage404( ) ( $host = "http://".$_SERVER["HTTP_HOST"]."/"; header("HTTP/1.1 404 Not Found"); header("Status: 404 Not Found"); header(" Location:".$host."404"); ) )


I note that the class implements very simplified logic (despite the voluminous code) and perhaps even has security problems. This was done intentionally, because. writing a full-fledged routing class deserves at least a separate article. Let's take a look at the main points...

The element of the global array $_SERVER["REQUEST_URI"] contains the full address to which the user applied.
For example: example.ru/contacts/feedback

Using the function explode the address is divided into components. As a result, we get the name of the controller, for the example given, this is the controller contacts and the name of the action, in our case - feedback.

Next, the model file is connected (the model may be missing) and the controller file, if any, and finally, the controller instance is created and the action is called, again, if it was described in the controller class.

Thus, when you go, for example, to the address:
example.com/portfolio
or
example.com/portfolio/index
The router will do the following:

  1. connect the model_portfolio.php file from the models folder containing the Model_Portfolio class;
  2. include the controller_portfolio.php file from the controllers folder containing the Controller_Portfolio class;
  3. will create an instance of the Controller_Portfolio class and call the default action - the action_index described in it.
If the user tries to access the address of a non-existent controller, for example:
example.com/ufo
then it will be redirected to the 404 page:
example.com/404
The same will happen if the user accesses an action that is not described in the controller.

2.2. Returning to the MVC Implementation

Let's go to the core folder and add three more files to the route.php file: model.php, view.php and controller.php


Let me remind you that they will contain base classes, which we will now start writing.

File contents model.php
class Model ( public function get_data() ( ) )
The model class contains a single empty data fetch method that will be overridden in descendant classes. When we create descendant classes, everything will become clearer.

File contents view.php
class View ( //public $template_view; // here you can specify the default public view. function generate($content_view, $template_view, $data = null) ( /* if(is_array($data)) ( // convert array elements into variables extract($data); ) */ include "application/views/".$template_view; ) )
It is not difficult to guess that the method generate designed to form a view. The following parameters are passed to it:

  1. $content_file - views displaying page content;
  2. $template_file - common template for all pages;
  3. $data is an array containing the content elements of the page. Usually filled in the model.
The include function dynamically connects a common template (view), inside which the view will be embedded
to display the content of a particular page.

In our case, the general template will contain a header, menu, sidebar and footer, and the content of the pages will be contained in a separate form. Again, this is done for simplicity.

File contents controller.php
class Controller ( public $model; public $view; function __construct() ( $this->view = new View(); ) function action_index() ( ) )
Method action_index is the action called by default, we will override it when implementing descendant classes.

2.3. Implementing Model and Controller descendant classes, creating View's

Now the fun begins! Our business card site will consist of the following pages:
  1. home
  2. Services
  3. Portfolio
  4. Contacts
  5. And also - page "404"
Each page has its own controller from the controllers folder and a view from the views folder. Some pages may use a model or models from the models folder.


The file is highlighted in the previous figure. template_view.php is a template that contains common markup for all pages. In the simplest case, it could look like this:
home
To give the site a presentable look, we will make up a CSS template and integrate it into our site by changing the structure of the HTML markup and including CSS and JavaScript files:

At the end of the article, in the “Result” section, there is a link to a GitHub repository with a project in which the integration of a simple template has been done.

2.3.1. Creating the main page

Let's start with the controller controller_main.php, here is its code:
class Controller_Main extends Controller ( function action_index() ( $this->view->generate("main_view.php", "template_view.php"); ) )
The method generate instance of the View class, the names of the files of the general template and the view with the content of the page are passed.
In addition to the index action, the controller can of course contain other actions.

We discussed the file with a general view earlier. Consider a content file main_view.php:

Welcome!

OLOLOSHA TEAM is a team of first-class website development specialists with many years of experience in collecting Mexican masks, bronze and stone statues from India and Ceylon, bas-reliefs and sculptures created by the masters of Equatorial Africa five or six centuries ago...


This contains simple markup without any PHP calls.
To display the main page, you can use one of the following addresses:

An example of using a view that displays data received from the model will be discussed later.

2.3.2. Creating a Portfolio Page

In our case, the Portfolio page is the only page that uses the model.
The model usually includes data fetching methods, for example:
  1. methods of native pgsql or mysql libraries;
  2. methods of libraries that implement data abstraction. For example, methods of the PEAR MDB2 library;
  3. ORM methods;
  4. methods for working with NoSQL;
  5. and etc.
For simplicity, we will not use SQL queries or ORM statements here. Instead, we simulate real data and immediately return an array of results.
Model file model_portfolio.php put in the models folder. Here is its content:
class Model_Portfolio extends Model ( public function get_data() ( return array(array("Year" => "2012", "Site" => "http://DunkelBeer.ru", "Description" => "Dark Dunkel beer from the German manufacturer Löwenbraü produced in Russia by the brewing company "SUN InBev"."), array("Year" => "2012", "Site" => "http://ZopoMobile.ru", "Description" => "Russian-language catalog of Chinese Zopo phones based on Android OS and accessories.", // todo); ) )

The model controller class is contained in the file controller_portfolio.php, here is its code:
class Controller_Portfolio extends Controller ( function __construct() ( $this->model = new Model_Portfolio(); $this->view = new View(); ) function action_index() ( $data = $this->model->get_data( ); $this->view->generate("portfolio_view.php", "template_view.php", $data); ) )
into a variable data the array returned by the method is written get_data, which we considered earlier.
This variable is then passed as a method parameter. generate, which is also passed: the name of the file with the common template and the name of the file containing the view with the page content.

The view containing the content of the page is in a file portfolio_view.php.

Portfolio

All projects in the following table are fictitious, so do not even try to follow the links provided. "; } ?>
YearProjectDescription
".$row["Year"]."".$row["Site"]."".$row["Description"]."


Everything is simple here, the view displays the data received from the model.

2.3.3. Creating the rest of the pages

The rest of the pages are created in the same way. Their code is available in the repository on GitHub, a link to which is provided at the end of the article, in the “Result” section.

3. Result

And here's what happened in the end:

Screenshot of the resulting business card site



Link to GitHub: https://github.com/vitalyswipe/tinymvc/zipball/v0.1

But in this version, I sketched the following classes (and their corresponding types):

  • Controller_Login in which a view is generated with a form for entering a login and password, after filling which an authentication procedure is performed and, if successful, the user is redirected to the admin panel.
  • Contorller_Admin with an index action that checks whether the user was previously authorized on the site as an administrator (if so, the admin view is displayed) and a logout action for logging out.
Authentication and authorization is a different topic, so it is not covered here, but only the link indicated above is provided so that there is something to build on.

4. Conclusion

The MVC pattern is used as an architectural basis in many frameworks and CMS, which were created in order to be able to develop qualitatively more complex solutions in a shorter time. This was made possible by increasing the level of abstraction, since there is a limit to the complexity of the structures that the human brain can operate on.

But using web frameworks such as Yii or Kohana, which consist of several hundred files, when developing simple web applications (for example, business card sites) is not always advisable. Now we can create a beautiful MVC model so as not to mix Php, Html, CSS and JavaScript code in one file.

This article is more of a starting point for learning CMF than an example of something truly correct that you can take as the basis of your web application. Perhaps it even inspired you and you are already thinking of writing your own microframework or CMS based on MVC. But, before inventing the next wheel with "blackjack and whores", think again, maybe it would be wiser to direct your efforts to the development and to help the community of an existing project?!

P.S.: The article has been rewritten taking into account some of the comments left in the comments. Criticism has been very helpful. Judging by the response: comments, personal appeals and the number of users who added the post to their favorites, the idea to write this post turned out to be not so bad. Unfortunately, it is not possible to take into account all the wishes and write more and in more detail due to lack of time ... but perhaps those mysterious personalities who minus the original version will do this. Good luck with your projects!

5. A selection of useful links on the subject

The article very often touches on the topic of web frameworks - this is a very extensive topic, because even microframeworks consist of many components that are cleverly linked to each other and it would take more than one article to talk about these components. However, I decided to give here a small selection of links (which I went to when writing this article) that in one way or another relate to the topic of frameworks.

Tags: Add tags

In this lesson, we will look at techniques for passing data between forms and pages. These methods are POST and GET. We will talk about each separately and in more detail. Generally speaking, it is necessary for communication between forms. For example, we fill in some fields on the page and we need to transfer them to another page for processing.

GET method in PHP

Let's start with the GET method. This is when all variables and their values ​​are passed directly through the address. Now, with an example, you will see everything, and even understand how most sites and forums work.
For example, we have an html page like this:

Page with an example of passing variables using Get link

See the link? It is complex and consists of several parts. Let's break it all down:
https://site- the address of the domain or, as it is also called, the host.
index.php- the php page that will process the request.
? - a symbol of separation between the address and the block with variables.
Next come the variables and their values, which are separated by the symbol & .
name=Sergey— variable name and its value Sergey.
age=22- the same, the variable age, value 22.

All sorted, now let's see how it is processed in php using the GET method.
The index.php page, as you remember, we passed it:

First advice: ALWAYS check variables for correctness: for emptiness, for compliance with valid values, and so on. Since everything is transmitted through the address bar, the data can be easily changed and harm the site. Now for the code itself: we, with the help of , checked the name and age variables for emptiness and, if they are not empty, then displayed them, and if empty, then simply reported it.
Everything is simple, agree? For example, you can create an html page and make links through variables in the menu, and process the variable in index.php and display one or another page depending on the value received. Well, we'll talk about this later, in an article about creating a site in php from scratch. In order not to miss anything, I advise you to subscribe to RSS.

POST method in PHP

To demonstrate how this method works, we need a little more than a simple line with an address :) You will need to create an html page with a form to fill out, but that's okay, I'll give a ready-made example for you:

Page with an example of passing variables using Post

Fill in the fields for information transfer:

Enter your name:

Enter your age:

So, we have created an html page with a simple form. Remember, the POST method can only be used on a form.
The first parameter of the form is "method", it defines the method we will use to submit. As you might guess, it's either GET or POST. In this case, if GET is set, then all field names (in the form of variable names), as well as their values, are passed by reference, as in the section about the GET method. If POST is set, then all variable names and values ​​will be transmitted as a browser request to the web server. That is, they will not be visible in the address bar. In many cases this is very useful. POST is also safer, which is understandable, because variables with their values ​​​​are no longer so easy to edit, although it is also possible.

The second form parameter is "action". This is the path and filename of the script to which we are passing data. In our case, this is index.php. This path can also be passed in full, that is, like this: action="https://my_site.ru/index.php". If you do not specify the value of the “action” parameter, then all information will be transferred to the main script, that is, the index.php index page of your site, which is quite logical.

Now we will receive the data from our form. Since we passed it to index.php, then the code of this particular page will be below:

"; echo "name - "; echo $_POST["user_name"]; echo "
age - "; echo $_POST["age"]; echo " years"; ) else ( echo "Variables did not reach. Check everything again."; ) ?>

Do not forget to check for emptiness and valid values. Next, we need to clarify why our variables are called exactly user_name and age? And you look at the form fields that we created above. See there input name="user_name" type="text"? This is where the name parameter sets the name of the variable that we will receive using this field. It's the same with age. I hope it's clear. Well, getting a variable and its value via POST is almost the same as GET, which we discussed above.

Well, the lesson turned out to be big, but one of the most useful, because passing variables between forms and pages is exactly the interactivity for which we use PHP.

January 26 , 2017

Promises, promises, callback hell, Deferred object, thens, whens and resolutions...
These words come from every telegraph pole. There is a feeling that I am the last fool on this planet who does not use promises. Having become sad about this, I started to deal with this topic. But as in the case of git rebase, it turned out that there is a lot of information about promises on the Internet, but there is little explanation on the fingers. And since something is not on the Internet, you need to create it yourself.

I have little interest in the general theory and subtleties of this wonderful thing. I perceive any unfamiliar thing from the point of view of the possible benefit from it. Therefore, it is better to look for detailed explanations of the mechanism of how promises work in other blogs. We will look at an example of practical work with jquery promises. And in the end, we will find out whether it is worth dealing with this in more detail, or whether it is still legal to write tons of code in $.ajax.success.

When do we need promises at all?

A typical example. We are looking into a new project where good backend developers have created a bunch of small API requests that perform strictly defined actions. One request - one small operation. In general, this is how it should be when building a REST service. But for now, let's leave the show-offs about REST and go down to earth.

In our imaginary project, there is an entity User, in a programming way user, and Order - order. And there are several get and post requests that perform operations like this:

  • 1. GET php/user/info.php- getting information about the user
  • 2. GET php/user/orders.php- receiving user orders
  • 3. POST php/user/new.php- adding a new user
  • 4. POST php/order/new.php- adding an order
  • 5. POST php/order/applyBonus.php- applying a bonus to an order (writing off a certain amount from a specific order)

These are simple queries that do simple database manipulation. And at the front, we have a set of various tasks that come down to executing certain requests, waiting for a response from them, parsing and passing this response to the next request.

Even if we need to send one request and execute some code in success, it doesn't look very good. What if we take the result of the first request and send it to the second? And if you need to sequentially execute 2 requests, wait for the data to be received, use this data in the third one, and only after that perform some actions? The horror is complete.

You ask: what are these wild requirements, where you need to sequentially perform 3 ajax requests and only then perform some useful action? And now we will outline a dozen tasks for which we will use promises. The tasks will become more complicated and this very topic will be the last one out of three consecutive requests. And most importantly, we will write code that will allow us to sanely manage any combination of ajax requests. Let's get started.

What tasks will we solve?

I'm posting the whole list:

  • 1. Simple getting information about the user - 1 get request
  • 2. Getting a list of user orders - 1 get request
  • 3. Receiving the last order - 1 get-request + data processing on the client
  • 4. The total amount of all user orders - 1 get-request + data processing on the client
  • 5. Adding a user - 1 post request
  • 6. Adding an order - 1 post-request
  • 7. Adding an order with user registration - 2 post-requests. The second query uses the results of the first.
  • 8. Applying a bonus to an order - 1 post-request
  • 9. Applying the bonus to the user's last order - 2 get and 1 post request

As you can see, most of the tasks are not very difficult. They can be easily solved with the usual $.ajax.success callbacks. But firstly, your code will be quickly cluttered, secondly, it will be difficult to reuse it, and thirdly, just imagine what kind of footcloth you have to write to solve problem number 9 with three consecutive requests.

We will gradually complicate the tasks in order to get used to the possibly unusual javascript code. Interface looks like this: for each of the above tasks, we will create one button. Clicking on the button will launch the desired sequence of requests corresponding to the task number.

Of course, not very impressive, but we need not to bring beauty, but to understand how to make promises work for our benefit. The pathetic title of the article is quite consistent with its content. We will write several js modules that will allow you to perform and combine any tasks you want using just 5 api requests to the server. But first, this server code needs to be written.

We write server code. Of course PHP

Let's quickly sketch out 5 php scripts. And at the same time we will analyze what parameters they will accept and what to return. In the root of the project, we will create a php folder, and in it 2 more - user and order. Let's take a look at all the request files one by one.

PHP. Getting information about a user

GET /php/user/info.php

If ($_SERVER["REQUEST_METHOD"] === "GET") ( // Receive data from the $_GET array $userId = (int)$_GET["userId"]; // Extract user information from the database... sleep(1); // Return the user's email and bonus echo json_encode(array("email" => " [email protected]", "bonus" => rand(500, 1000)))); ) else ( header("HTTP/1.0 405 Method Not Allowed"); echo json_encode(array("error" =>

We take the userId parameter as input, then wait 1 second (simulate some useful work) and return the user's email and bonus in a json object. We will generate a bonus randomly so that it is not so sad to keep track of the same numbers.

There is one point: if we try to access the script with a non-GET request, we will return a 405 http error with the corresponding text in the error field. Ignore it for now, we will mention it at the end of the article.

Of course, in real life you will have code that climbs into the database, but this post is not about that. Now we just need to simulate requests, paying a little attention to the input parameters and the format of the responses from the server. All this will come in handy when writing js code with promises. We will write the remaining 4 API requests by analogy.

PHP. List of user orders

GET /php/user/orders.php

If ($_SERVER["REQUEST_METHOD"] === "GET") ( // Receive data from the $_GET array $userId = (int)$_GET["userId"]; // Extract user information from the database... sleep(1); // Return user orders echo json_encode(array("orders" => array(array("orderId" => rand(1, 5), "summa" => rand(1000, 1500)), array ("orderId" => rand(10, 20), "summa" => rand(2000, 5000)), array("orderId" => rand(30, 50), "summa" => rand(10000, 20000) )))); ) else ( header("HTTP/1.0 405 Method Not Allowed"); echo json_encode(array("error" => "Method not supported")); )

We accept userId, return an array of objects with two fields: id and order amount.

PHP. User creation

POST /php/user/new.php

If ($_SERVER["REQUEST_METHOD"] === "POST") ( // Accepting data from the $_POST array $email = $_POST["email"]; $name = $_POST["name"]; // Like add the user to the database... sleep(1); // Return the id of the created user, for example - random echo json_encode(array("userId" => rand(1, 100))); ) else ( header("HTTP/ 1.0 405 Method Not Allowed"); echo json_encode(array("error" => "Method not supported")); )

At the input from the $_POST array, we take the email and name, return the id of the created user.

PHP. Adding an order

POST /php/order/new.php

If ($_SERVER["REQUEST_METHOD"] === "POST") ( // Accepting data from the $_POST array $userId = (int)$_POST["userId"]; $summa = (int)$_POST["summa "]; // Add an order to the database... sleep(1); // Return the id of the created order, for example - random echo json_encode(array("orderId" => rand(1, 1000))); ) else ( header("HTTP/1.0 405 Method Not Allowed"); echo json_encode(array("error" => "Method not supported")); )

We accept the user id and the amount of the order, return the id of the created order.

PHP. Applying a bonus to an order

POST /php/order/applyBonus.php

If ($_SERVER["REQUEST_METHOD"] === "POST") ( // Accept data from $_POST array $orderId = (int)$_POST["orderId"]; $bonus = (int)$_POST["bonus "]; // Add order to database... sleep(1); // Return success code echo json_encode(array("code" => "success")); ) else ( header("HTTP/1.0 405 Method Not Allowed"); echo json_encode(array("error" => "Method not supported")); )

At the entrance - the order id and the amount of the bonus, the exit is just a success code, they say, everything went well.

With the preparation finished, let's move on to the client side and the js code.

Project frame and html-preparation

Let's create an index.html file at the root of the project. In the head section, write this

Promise jQuery

Some styling right in the head section - so be it. Let's put the following in the body.

Promise jQuery

1. General information about the user 2. List of orders 3. Last order 4. Total amount of all orders 5. Adding a user

6. Adding an order 7. Adding an order with user creation 8. Applying a bonus to an order 9. Applying a bonus to a user's last order

Here we see 9 buttons, by clicking on which the above operations are launched. Each time the tasks will become more difficult. We will implement the functionality sequentially. And in the end, we are surprised to note that we wrote a function that waited for two ajax requests to be completed and used their results in the third one, and after the completion of the third one, did something else ... It sounds hard, but it will turn out to be no more difficult than sending one- single get request for data. The amount of code for all 9 tasks will be approximately the same - ten simple lines each.

4 js files are included at the end of index.html. jquery, drop it into the js folder immediately, leave the user.js and order.js files empty for now. And with main.js we will work a little and write the basic initialization code for our application.

stub main.js

The general structure of the file is

// Main application module "use strict"; var app = (function() ( var userId = Math.round(Math.random() * 100); // Get information about the user function _userInfo() ( // ... ) // Get the list of user orders function _userOrders( ) ( // ... ) // Last user order function _userLastOrder() ( // ... ) // Total sum of all orders function _userTotalSummaOrders() ( // ... ) // Adding a user function _userNew() ( // ... ) // Adding an order function _orderNew() ( // ... ) // Adding an order with user creation function _orderNewWithUser() ( // ... ) // Applying a bonus to an order function _orderApplyBonus() ( // ... ) // Applying the bonus to the user's last order function _userApplyBonusToLastOrder() ( // ... ) // Catching errors in promises function onCatchError(response) ( // ... ) // Attaching events to buttons function _bindHandlers() ( $("#user-info-btn").on("click", _userInfo); $("#user-orders-btn").on("click", _userOrders); $("# user- last-order-btn").on("click", _userLastOrder); $("#user-total-summa-orders-btn").on("click", _userTotalSummaOrders); $("#user-new-btn").on("click", _userNew); $("#order-new-btn").on("click", _orderNew); $("#order-new-with-user-btn").on("click", _orderNewWithUser); $("#order-apply-bonus-btn").on("click", _orderApplyBonus); $("#user-apply-bonus-to-last-order-btn").on("click", _userApplyBonusToLastOrder); ) // Application initialization function init() ( _bindHandlers(); ) // Return outside return ( onCatchError: onCatchError, init: init ) ))(); // Start the application $(document).ready(app.init);

Here we have prepared the app module - the main module of the application. The userId variable, global for the entire module, is taken randomly for the example. The following are 9 functions that are launched by pressing the corresponding buttons. By the comments you can easily track them.

The onCatchError function fires when something goes wrong in ajax requests. _bindHandlers hooks all 9 click events, init is the basic module initialization function. The return block returns the facade of the module, that is, methods that are accessible from outside. onCatchError is returned outside because it will be used in the user and order modules. Next comes $(document).ready(app.init) - this starts the application.

The scheme is as follows: the actual code that receives data and sends it to the server will be located in the user.js and order.js modules. That is, there is the main implementation, the core of the application. And in main.js, in the appropriate places where //... is now located, several lines of code will be written using the capabilities of the user and order modules.

Don't worry if you haven't smoked it to the end, now everything will become clear.

Getting user information

The first task is to send an ajax request to the server, get the info and do something with it. It would seem that there is something to be smart about? We wrote like this a thousand times

$.ajax(( url: "php/user/info.php", data: ( userId: userId ), method: "get", dataType: "json", success: function(response) ( // Do something with response ), error: function(response) ( // Do something on error ), ));

But it was not for nothing that we wanted to deal with promises, and even washed down a whole project for this, albeit a small one. In addition, $.ajax already returns a promise and it would be a sin not to use it. Therefore, let's take a breath and write a slightly different code.

FILE /js/user.js

// Module User "use strict"; var user = (function() ( // General information about the user // Return the object - email and user bonus function getInfo(params) ( return $.ajax(( url: "php/user/info.php", data: ( userId: params.userId ), method: "get", dataType: "json" )).then(function(data) ( return ( email: data.email, bonus: data.bonus ); )).fail(app. onCatchError); ) // Return outside return ( info: getInfo ) ))();

This is the skeleton of the user module and the first function to use promises. Looks very familiar - same $.ajax, but no callback function to start with. Instead, we specify what data to return as a result of the query. In the then ... return block, we say that we expect an object from the promise from two fields: email and bonus. As we remember, they are the ones that are returned from the backend upon request php/user/info.php.

getInfo(params) - we pass an object with parameters to the function. In our case, it is the only one, userId, but you understand that the object is convenient in that you can transfer as much data as you like in it. Therefore, in the future we will use objects as parameters. However, and as a response from the server, too.

Fail(app.onCatchError) tells you to call the app module's onCatchError method if something goes wrong with the request. The problems can be different: a server-side error, an incorrect request method, or an invalid url. Ignore what this onCatchError method does for now, we'll figure it out at the end of the article.

Also note that the user.info method knows nothing about how the data will be used. Its task is to return the data, and what to do with them, let the code that called user.info figure it out. And in my opinion, this is cool, because you can see a clear separation of logic. The user module knows how to interact with the server, but it doesn't know what the browser wants from it. This module can be thought of as a layer between the javascript manipulations from the browser dom and our backend. Just like php code is the same layer between the client browser and the database.

But we got carried away contemplating the module and getInfo. Let's take a look at how to use it in practice - it's in the _userInfo function of the app module. We carefully wrote a stub for it, let's fill it with code

// Get information about the user function _userInfo() ( user.info((userId: userId)).done(function(userInfo) ( console.log("userInfo: ", userInfo); )); )

And this is all that is called when the button is clicked! We pull the info method of the user module. Not getInfo is an internal function. It is info, since we specified it in user in the return (...) block

user.info((userId: userId)) returns a promise to us. Further along the chain, we call done with a callback function, into which we pass the result of the promise. Let me remind you that this is an object of the form (email: " [email protected]", bonus: 12345). We won't do anything special with this result, we'll just display it in the console. But no one bothers you to perform some manipulations with this data. It is even possible to allocate a separate function for this, into which you pass the userInfo object.

Let's look again at the resulting code and compare it with the usual $.ajax request and perform all the necessary manipulations in success-e. It seems that the code is the same, except that it has become a little larger. But something did change. The code has become cleaner. It was divided into 2 logical parts: data acquisition and data processing. Less investment. Functions are shorter.

If you still do not believe in the practical benefits of this approach, then below I can convince you of this. But first, let's write the code to get a list of the user's orders - a carbon copy of the previous example.

Get a list of user orders

Add the following code to the user module

// List of user orders // Return an array of orders in the format (order id, order amount) function getOrders(params) ( return $.ajax(( url: "php/user/orders.php", data: ( userId: params.userId ), method: "get", dataType: "json" )).then(function(data) ( return data.orders; )).fail(app.onCatchError); ) return ( info: getInfo, orders: getOrders, )

And in main.js, fill in another function with code

// Get the list of user orders function _userOrders() ( user.orders((userId: userId)).done(function(userOrders) ( console.log("userOrders: ", userOrders); )); )

There is nothing new here, we sent a request, received a list of orders, displayed it in the console. But in the next paragraph, it will be much more interesting.

Get the user's last order

Suppose we need information not about all orders, but about one, the last one. The backend API only allows us to get all the orders in their entirety, in an array. There are no problems, we can easily pull out a separate order object on the client, but how to organize this?

Of course, the same $.ajax would help us, but how much code would we have to save up and paste in this case? We don’t know how often the last order will have to be pulled out and to which functions to send the received data. Of course, you can streamline any code, write wrapper functions for ajax requests, and pass the necessary result handler functions to $.ajax.success ... But you must admit, this is not the most convenient way, although it is familiar

Therefore, in the light of mastering promises, we will make such a feint with our ears and write one more function in the user module

// User's last order // Return the last order (object) function getLastOrder(params) ( return getOrders(params).then(function(orders) ( return orders.slice(-1); )); ) return ( info: getInfo , orders: getOrders, lastOrder: getLastOrder, )

Look what we've done. We took a ready-made method that returns another promise that receives a list of orders. The orders in .then(function(orders)) gets exactly the array of orders received from the backend. Then it only remains to return the last element of the array through return orders.slice(-1);

The advantage of such a chain can already be seen here. The getLastOrder function doesn't even know how all orders are retrieved. There is no ajax request visible here, this is handled by another method. We only use the finished result.

And using this in main.js when the button is clicked is just as easy as the previous examples.

// User Last Order function _userLastOrder() ( user.lastOrder((userId: userId)).done(function(lastOrder) ( console.log("userLastOrder: ", lastOrder); )); )

Let's write another function for pinning.

Getting the sum of all user orders

// Total sum of orders function getTotalSummaOrders(params) ( return getOrders(params).then(function(orders) ( return orders.reduce(function(total, currOrder) ( return total + currOrder.summa; ), 0); )) ; ) return ( info: getInfo, orders: getOrders, lastOrder: getLastOrder, totalSummaOrders: getTotalSummaOrders )

// Total sum of all orders function _userTotalSummaOrders() ( user.totalSummaOrders((userId: userId)).done(function(totalSumma) ( console.log("userTotalSummaOrders: ", totalSumma); )); )

We used the same receiver. We took the ready-made getOrders(params) method and calculated the amount of orders using reduce. And again, instead of a dozen lines of code with callbacks, we see 2 short and distinct functions. In addition, user.totalSummaOrders() can continue to be used anywhere, without worrying about how the data returned to it will be used.

Adding a new user

// Adding a user // Returning the id of the created user function newUser(params) ( return $.ajax(( url: "php/user/new.php", data: ( // some data about the new user email: params. email, name: params.name ), method: "post", dataType: "json" )).then(function(data) ( return data.userId; )).fail(app.onCatchError); ) return ( info: getInfo, orders: getOrders, lastOrder: getLastOrder, totalSummaOrders: getTotalSummaOrders, newUser: newUser )

// Adding a user function _userNew() ( var data = ( email: " [email protected]", name: "Webdevkin" ); user.newUser(data).done(function(userId) ( console.log("userNew: ", userId); )); )

It looks a little longer, but only because we are passing an object of two fields to the server. The rest of the code is as simple as getting the information. Don't forget that method: "post" is already used here

And now even more interesting, let's start combining ajax requests. Let's create a new order.js module.

Adding an order

// Module Order "use strict"; var order = (function() ( // Adding an order // Returning the id of the created order function _createOrder(params) ( return $.ajax(( url: "php/order/new.php", data: ( // some new order data userId: params.userId, summa: params.summa ), method: "post", dataType: "json" )).then(function(data) ( return data.orderId; )).fail(app. onCatchError); ) // Adding an order // Returning the id of the created order function newOrder(params) ( if (params.userId) ( // userId is known, we immediately add the order return _createOrder(params); ) else ( // You need to create a user first return user.newUser(params).then(function(userId) ( return _createOrder((userId: userId, summa: params.summa)); )); ) ) // Return outside return ( newOrder: newOrder ) ))() ;

There are 2 functions here. _createOrder is directly adding an order. Works similarly with adding a user. We will not call this method directly from outside - all work will go through newOrder.

What is so special about her? We need a little digression here...

How does online ordering work?
Often, ordering takes place in two ways: for registered users and guests. For those registered, everything is clear: we take the userId (as a rule, it is stored on the backend in the session), we transfer order data from the client, we add the order to the database. Lepota.
But if a "guest" has entered the site, then we must also ensure that the order is placed. But you still need to get a user on it, the order needs to be stupidly tied to someone. You can force a person to separately register on the site, return to checkout, and, as a full-fledged user, send order data to the server.
Or you can act humanely: collect a minimum of user data (name + email), order data and in one fell swoop and register a user and add an order to him. No unnecessary gestures - more joy for the buyer.

In short, it turns out that the newOrder function can perform not only adding an order, but also pre-adding a user. This necessity is verified by the presence of the userId parameter in params. If it is not there, then we first add the user, get his userId and start adding the order _createOrder with him

If params.userId is known, then _createOrder is run directly. Sufficiently complex manipulations are implemented by the same 5-6 lines of code. Each time we take on more and more complex tasks, but we do not observe a significant complication of the code. A set of simple methods that return promises allows us to combine queries in any way and assemble complex functionality like a Lego constructor.

Let's check how adding an order to app.js works - let's write handlers for two more buttons in main.js.

// Adding an order function _orderNew() ( var data = ( userId: userId, summa: 7000 ); order. newOrder(data).done(function(orderId) ( console.log("orderCreate: ", orderId); )) ; ) // Adding an order and creating a user function _orderNewWithUser() ( var data = ( email: " [email protected]", name: "Webdevkin", summa: 10000 ); order.newOrder(data).then(function(orderId) ( console.log("orderNewWithUser: ", orderId); )); )

Apply bonus to order

// Apply bonus to order // Return true if successful function applyBonus(params) ( return $.ajax(( url: "php/order/applyBonus.php", data: ( orderId: params.orderId, bonus: params. bonus ), method: "post", dataType: "json" )).then(function() ( return true; )).fail(app.onCatchError); ) // Return outside return ( newOrder: newOrder, applyBonus: applyBonus )

// Applying the bonus to the order function _orderApplyBonus() ( order.applyBonus((orderId: 5, bonus: 200)).then(function(result) ( console.log("orderApplyBonus: ", result); )); )

Everything is familiar here. We need a function as an intermediate to demonstrate how to make 3 ajax requests. You just need to come up with a case of needing 3 requests in a row.

Yes Easy! Let's fantasize. On the birthday of the company, the management decided to accrue a bonus to all its customers and write off this amount from the user's last order. How the bonus was accrued is none of our business, we need to get the bonus amount and the id of the last order. These are 2 get requests that can be executed in parallel. After waiting for this data, we will send another (third) request to the server, already post, which will apply the specified bonus to the desired order.

An example, of course, far-fetched, but for the sake of art it will do. We write code.

Applying a bonus to the user's last order

// Apply bonus to last order // Return true if successful function applyBonusToLastOrder(params) ( var infoPromise = this.info(params), lastOrderPromise = this.lastOrder(params); return $.when(infoPromise, lastOrderPromise).then (function(userData, lastOrderInfo) ( return order.applyBonus(( orderId: lastOrderInfo.orderId, bonus: userData.bonus )); )); ) return ( info: getInfo, orders: getOrders, lastOrder: getLastOrder, totalSummaOrders: getTotalSummaOrders, newUser: newUser, applyBonusToLastOrder: applyBonusToLastOrder )

Here we have used the new $.when construct. The point is that we pass several promises to it, wait for their results, and pass the callback to the then function - what we want to do with these results. In our case, we extract the user's bonus from the first one, and the id of the last order from the second one. And we give this data to the third method. And that's it.

This was the last and most convoluted use of promises in our test project. Think how much code we would have written for such a sequence before, without knowing how to use promises.

We check the work in main.js like this

// Applying the bonus to the user's last order function _userApplyBonusToLastOrder() ( user.applyBonusToLastOrder((userId: userId)).then(function(result) ( console.log("userApplyBonusToLastOrder: ", result); )); )

Error processing

Our world is not perfect, and at any stage the request may be interrupted. We need to somehow handle such cases and remember the app.onCatchError method. Let's look at its implementation

// Catching errors in promises function onCatchError(response) ( var json = response.responseJSON, message = (json && json.error) ? json.error: response.statusText; console.log("ERROR: ", message); )

What's going on here? In the response parameter, we will find information about the error that has overtaken us. We first pull the contents of the response into a json object. And then we check it for the presence of an incomprehensible error field.

If we return to the php code section, we will see that there is nothing mysterious in this field. We ourselves give it back from the backend in case of a method mismatch (http code 405). When, for example, we try to create a user using the GET method. I wrote this php code solely to show that we ourselves can generate adequate error causes and report them to users. In the same way, such a response can be thrown if an error occurs during the validation of input parameters or during writing to the database.

Thus, in json.error we will find a description of the error generated on the backend. But this will work in cases where the request has reached the server. If we made a mistake, for example, with a URL or the server simply does not respond, then in this case we analyze the regular response of the xhr response.statusText object.

What to do with this error information is up to you. Often some kind of unobtrusive message like "something went wrong" is shown. For example, we will write console.log("ERROR: ", message) and this will end the article.

Results, demo and sources

I hope I convinced you that promises are worth looking at, not just for the sake of an extra line in the resume. Of course, in the article we did not even closely consider all their capabilities. In fact, we only touched on a special case - promises returned by the standard $.ajax method, and briefly - $.when. But on the other hand, they made sure that you can work with this and use them to write simple and easily extensible code for quite diverse and complex tasks.

Here you can review the code. And look here. Just by clicking on the buttons, you will not see anything. Open the console, the Network tab, and look at the order in which queries are executed, the data passed, and the results returned. Make sure that in several consecutive requests all parameters are passed correctly.

And of course, share your own thoughts and considerations in the comments.

Views