Автор Анна Евкова
Преподаватель который помогает студентам и школьникам в учёбе.

Основные правила работы с функциями: примеры и ограничения использования функций в различных языках программирования

Содержание:

Введение

Функции являются одними из главных концептов в программировании. Они позволяют разделять программы на так называемые подпрограммы, что, в свою очередь, позволяет коду быть модулярнее и проще. Функции также решают проблему повторения кода (англ. code repetition).

Концепт функций в программировании не нов. Ещё в языке Ассемблера присутствовали так называемые процедуры, которые очень похожи на функции в современных языках программирования. Существуют целые функциональные языки программирования, где функция – основной структурный элемент программы. Таким образом, функции – одни из важнейших элементов программирования. Они используются повсюду. Вы не найдёте не одной серьёзной программы, в которой нет функций.

В этой работе мы рассмотрим основные правила, теорию и практическое использование функций в языках JavaScript и С#.

В работе использованы три источника: книга ‘Eloquent JavaScript’, Сайт ‘Mozilla Developers Network (MDN)’ и официальная документация языка C# от Microsoft. Кратко о достоверности этих источников:

  • Книга ‘Eloquent JavaScript’ признана одной из лучших книг по основам программирования и, в частности, JavaScript. Она довольно популярна у начинающих.
  • MDN – официальная документация веб-технологий от Mozilla, компании создавшей Firefox, популярный броузер. MDN очень популярен среди программистов JavaScript.
  • Официальная документация C# от Microsoft, создателя языка – самый надёжный источник знаний о C#.

Глава 1

Теория и базовое использование

1.1 Теория

Главная цель функций – разделение программ на подпрограммы. Так что такое подпрограммы? Подпрограммы – это части кода, отделённые от основной программы, которые получают входные данные и выдают (возвращают) выходные данные. Входные данные называют аргументами или параметрами, а выходные данные - возвращаемым значением (англ. return value). В отличии от математических функций, функции в программирование могут не принимать аргументы и не возвращать данные [1].

Каждая функция имеет имя, с помощью которого она может быть использована и так-называемое тело, в котором и написан сам код функции [2]. Тело функции может содержать любой код, который мог быть написан вне функции. Этот код может, в том числе, использовать другие функции и даже вызывать сам себя. Когда функция использует сама себя, это называется рекурсия (англ. recursion) [3], мы рассмотрим её позже.

Переменные, объявленные внутри кода функции, работают только внутри этой функции, однако функции могут менять значения глобальных переменных. Этот концепт называется область видимость переменный (англ. variable scope) [4].

Современные языки программирования имеют свои стандартные библиотеки. Стандартная библиотека – это набор часто-используемых функций, например синус или квадратный корень, которые уже написаны создателями языка. Функции стандартной библиотеки можно использовать не создавая их, они уже интегрированы в язык программирования.

1.2 Синтаксис

Теперь, давайте поподробнее разберём синтаксис функций. Начнём с JavaScript. Рассмотрим пример одной функции.

function add(a, b) {

return a + b;

}

  • ‘function’ – ключевое слово обозначающее создание функции.
  • ‘add’ – название функции.
  • ‘()’ – обозначает место для аргументов. Эти скобки должны присутствовать даже если функция не имеет аргументов.
  • ‘{}’ – обозначает тело функции.
  • ‘а, b’ – аргументы функции.
  • ‘return’ – обозначает возвращаемое значение.
  • ‘a + b’ – Возвращаемое значение. [5]

Таким образом эта функция принимает 2 аргумента и возвращает сумму этих двух аргументов.

Рассмотрим ещё одну функцию:

function getHalfPI() {

return Math.PI / 2;

}

Эта функция не принимает аргументов, но возвращает значение, в данном случае число ПИ делённое на 2 [6].

Рассмотрим третьею функцию:

function logSomething() {

console.log("Привет Мир, я из функции");

}

В данном случае функция не принимает аргументов и не возвращает значение, но пишет текст в консоль JavaScript.

Теперь, давайте рассмотрим пример использования вышеописанных функций. Начнём с функции ‘add’.

var sum = add(10, 9);

Таким образом мы присваиваем переменной sum значение, возвращённое функцией add, в данном случае – 19.

Перейдём к функции ‘getHalfPI’.

var halfPi = getHalfPI();

console.log(halfPi); // 1.5707963267948966

Здесь единственное отличие от первого примера – отсутствие аргументов у функции ‘getHalfPI’.

Рассмотрим пример функции ‘logSomething’

logSomething(); // Привет Мир, я из функции

В данном случае мы не присеваем значение переменной, так как функция ничего не возвращает, а просто выполняет функцию ‘console.log’, результат которой мы видим в консоли.

Перейдём на C#.

Рассмотрим аналог функции ‘add’.

int add(int a, int b) {

    return a + b;

}

  • ‘int’ – тип значения, возвращаемого функцией. Если функция ничего не возвращает тип должен быть ‘void’.
  • ‘add’ – название функции.
  • ‘()’ – обозначает место для аргументов. Эти скобки должны присутствовать даже если функция не имеет аргументов.
  • ‘{}’ – обозначает тело функции.
  • ‘int а, int b’ – аргументы функции с их типами.
  • ‘return’ – обозначает возвращаемое значение.
  • ‘a + b’ – Возвращаемое значение. [7]

Как видите, разница в синтаксисе объявления функций в JS и C# минимальна, так как оба языка C-подобные. Основная разница в том что в C# статическая типизация, а в JS динамическая, что означает что в C# мы должны указывать тип возвращаемого значения и аргументов, а в JS нет.

Также давайте рассмотрим аналоги функций ‘getHalfPI’ и ‘logSomething’.

double getHalfPI() {

    return Math.PI;

}

void logSomething() {

    Console.WriteLine("Привет Мир, я из функции");

}

Рассмотрим также примеры использования функций в C#.

int sum = add(10, 9);

double halfPi = getHalfPI();

Console.WriteLine(halfPi);

logSomething(); // Привет Мир, я из функции

1.3 Практический пример

Давайте рассмотрим пример одного алгоритма, где функции могут упростить код.

JavaScript:

var data = [1, 3, 6, 5, 4, 1, 2, 3, 5, 9];

// Задача: вычислить среднее значение массива 'data'

var total = 0;

for(var i = 0; i < data.length; i++) {

total = total + data[i];

}

// Результат:

var result = total / data.length;

А теперь то же самое, но с использованием функции:

var data = [1, 3, 6, 5, 4, 1, 2, 3, 5, 9];

// Функция для расчёта среднего значение массива

function arrayAverage(array /* входные данные */) {

// Имплементация алгоритма

var total = 0;

for(var i = 0; i < array.length; i++) {

total = total + array[i];

}

// Возвращение выходных данных

return total / array.length;

}

// Результат:

var result = arrayAverage(data);

В данном случае логика упростилась, однако сама программа стала длиннее. Но что, если на надо рассчитать среднее значение не одного массива, а сразу трёх?

Без функций:

var data1 = [1, 3, 6, 5, 4, 1, 2, 3, 5, 9];

var data2 = [9, 1, 9, 3, 3, 4, 3, 2, 1, 2];

var data3 = [5, 3, 4, 1, 7, 6, 4, 3, 2, 1];

var total1 = 0, total2 = 0, total3 = 0;

for(var i = 0; i < data1.length; i++) {

total1 = total1 + data1[i];

}

for(var j = 0; j < data2.length; j++) {

total2 = total2 + data2[j];

}

for(var k = 0; k < data3.length; k++) {

total3 = total3 + data3[k];

}

// Результат:

var result1 = total1 / data1.length;

var result2 = total2 / data2.length;

var result3 = total3 / data3.length;

С функциями:

var data1 = [1, 3, 6, 5, 4, 1, 2, 3, 5, 9];

var data2 = [9, 1, 9, 3, 3, 4, 3, 2, 1, 2];

var data3 = [5, 3, 4, 1, 7, 6, 4, 3, 2, 1];

function arrayAverage(array) {

var total = 0;

for(var i = 0; i < array.length; i++) {

total = total + array[i];

}

return total / array.length;

}

// Результат:

var result1 = arrayAverage(data1);

var result2 = arrayAverage(data2);

var result3 = arrayAverage(data3);

В данном случае код с функциями побеждает по двум фронтам: по чистоте и по размеру кода.

Рассмотрим этот-же пример в C#.

Без функций:

int[] data1 = { 1, 2, 3, 4 };

int[] data2 = { 2, 3, 4, 5 };

int[] data3 = { 3, 4, 5, 6 };

float total1 = 0, total2 = 0, total3 = 0;

for(int i = 0; i < data1.Length; i++) {

    total1 = total1 + data1[i];

}

for(int j = 0; j < data2.Length; j++) {

    total2 = total2 + data2[j];

}

for(int k = 0; k < data3.Length; k++) {

    total3 = total3 + data3[k];

}

float result1 = total1 / data1.Length;

float result2 = total2 / data2.Length;

float result3 = total3 / data3.Length;

C функциями.

int[] data1 = { 1, 2, 3, 4 };

int[] data2 = { 2, 3, 4, 5 };

int[] data3 = { 3, 4, 5, 6 };

float ArrayAverage(int[] array)

{

    float total = 0;

    for(int i = 0; i < array.Length; i++)

    {

        total = total + array[i];

    }

    return total / array.Length;

}

float result1 = ArrayAverage(data1);

float result2 = ArrayAverage(data2);

float result3 = ArrayAverage(data3);

Как видим, плюсы использования функций распространяются на C# так же, как и на JavaScript.

Вывод 1

Функции в программировании позволяют разделять код на подпрограммы и использовать их, используя имя. Функции могут принимать аргументы и возвращать значения. Они позволяют заметно укорачивать программы. Синтаксис функций очень похож в C-подобных языках. Стандартная библиотека – набор готовых функций, которые интегрированы в язык программирования.

Глава 2

Практические техники

2.1 Рекурсия

Рекурсия – это когда функция использует саму себя [8]. Это удобно, когда задачу можно разделить на несколько аналогичных, но более простых задач. Самый знаменитый пример рекурсии – функция факториала. Давайте рассмотрим варианты имплементации этой функции с рекурсией и без неё.

Сначала – имплементация без рекурсии, используя циклы.

function factorial(n) {

    var result = 1;

    for(var i = 1; i <= n; i++) {

        result = result * i;

    }

    return result;

}

А теперь давайте рассмотрим вариант имплементации с использованием рекурсии.

function factorial(n) {

    if(n === 1) return 1;

    return n * factorial(n - 1);

}

На первый взгляд очень трудно понять, что происходит в варианте с рекурсией. Для этого давайте пошагово разберём что делает код в обеих имплементациях, если на пример вызвать функцию с значением 3 (factorial(3)).

Начнём с варианта с циклами:

  1. Сначала значение переменной ‘result’ ровно 1
  2. Первая итерация цикла for. ‘i’ равен 1, ‘result’ равен 1. 1 * 1 = 1
  3. Вторая итерация цикла for. ‘i’ равен 2, ‘result’ равен 1. 1 * 2 = 2
  4. Третья итерация цикла for. ‘i’ равен 3, ‘result’ равен 2. 2 * 3 = 6

Итог: Результат равен 6

А теперь варианта с рекурсией:

  1. Первый вызов функции. n равен 3. Функция возвращает 3 * factorial(2).
  2. Второй вызов функции. n равен 2. Функция возвращает 2 * factorial(1).
  3. Третий вызов функции. n равен 1. Срабатывает if. Функция возвращает 1.
  4. Второй вызов возвращает 2 (2 * 1)
  5. Первый вызов возвращает 6 (3 * 2)

Итог: Результат равен 6

Рассмотрим ещё одну рекурсивную функцию, на сей раз вычисляющую степень числа.

Пример без рекурсии:

function pow(number, power) {

    var result = 1;

    for(var i = 0; i < power; i++) {

        result = result * number;

    }

    return result;

}

С рекурсией:

function pow(number, power) {

    if (power === 0) return 1;

    return number * pow(number, power - 1);

}

Пошагово разберём алгоритмы с входными данными number = 5, power = 3.

Сначала алгоритм без использования рекурсии:

  1. Сначала переменная result равна 1.
  2. Первая итерация. result равен 1 * 5 = 5.
  3. Вторая итерация. result равен 5 * 5 = 25.
  4. Третья итерация. result равен 25 * 5 = 125.

Итог: Результат равен 125

Вариант с рекурсией:

  1. Первый вызов функции. power равен 3. Функция возвращает 5 * pow(5, 2).
  2. Второй вызов функции. power равен 2. Функция возвращает 5 * pow(5, 1).
  3. Третий вызов функции. power равен 1. Функция возвращает 5 * pow(5, 0).
  4. Четвёртый вызов функции. power равен 0. Функция возвращает 1.
  5. Третий вызов возвращает 5
  6. Второй вызов возвращает 25
  7. Первый вызов возвращает 125

Итог: Результат равен 125

Рассмотрим ещё одну рекурсивную функцию, на сей раз работающую с массивами.

function searchArray(array, value, index) {

    var index = index || 0;

    

    if(index === array.length) return -1;

    if(array[index] === value) return index;

    return searchArray(array, value, index + 1);

}

Эта функция ищет индекс данного значения в массиве, и возвращает индекс если значение присутствует в массиве, если значение отсутствует функция возвращает значение -1.

Разберём эту функцию поподробнее. Начнём со второй линии кода.

var index = index || 0;

Этот код присваивает переменной ‘index’ значение аргумента ‘index’ или, если аугмент не передан функции, значение 0, то есть индекс первого элемента в массиве.

if(index === array.length) return -1;

Эта строка кода проверяет если индекс равен длине массива, то функция возвращает -1. Это значит, что данное значение не присутствует в массиве.

if(array[index] === value) return index;

Эта строка проверяет если значение в массиве по индексу равно значению поиска, то функция возвращает индекс, по которому было найдено значение.

return searchArray(array, value, index + 1);

Эта строка рекурсивно вызывает нашу функцию с теми же аргументами и индексом больше на 1. Таким образом функция ‘двигается’ по массиву, ища значение.

Теперь рассмотрим аналоги выше приведённых функций в C#.

Первая функция:

int factorial(int n)

{

    if (n == 1) return 1;

    return n * factorial(n - 1);

}

Вторая функция:

int pow(int number, int power)

{

    if (power == 0) return 1;

    return number * pow(number, power - 1);

}

Третья функция:

int searchArray(int[] array, int value, int index = -1)

{

    int _index = index == -1 ? 0 : index;

    if (_index == array.Length) return -1;

    if (array[_index] == value) return _index;

    return searchArray(array, value, _index + 1);

}

Как видите отличий в первых двух функциях минимальны. В третьей есть два различия. Первое различие – значение по умолчанию аргумента ‘index’, это самый простой способ чтобы вычислить был ли аргумент передан или нет. Мы просто проверяем, если значение аргумента равно значению по умолчанию, значит аргумент не был передан. В другом случае значение передано. Если не было бы значения по умолчанию – вызов функции без третьего аргумента вызвал бы ошибку. Второе различие – имя локальной переменной ‘_index’ вместо ‘index’. В C# нельзя называть переменную тем же названием что и аргумент.

2.2 Функции обратного вызова

Функции обратного вызова (англ. callback) – функции, которые передаются как аргументы другим функциям, которые вызывают их [9]. Эта техника очень удобна, когда мы хотим вызвать определённый код после окончания или во время выполнения других функций. Эта техника также позволяет писать высоко-настраиваемые функции, которые позволяют использовать логику другой функции как аргумент. На пример, у нас есть функция, рисующая график. Эта функция может принимать другую функцию и на основе неё настраивать график.

Вот псевдокод этой функции:

function drawGraphic(callback) {

    for(var x = 0; x < 1000; x++) {

        drawPoint(x, callback(x));

    }

}

Таким образом мы можем передать этой функции другую функцию. Например:

drawGraphic(Math.sin);

Функция может также быть написана нами.

function customFunction(x) {

    // Здесь могут быть любые расчеты,

    // Это всего лишь пример

    return x * x + 26;

}

drawGraphic(customFunction);

Таким образом мы получаем настраиваемую (customizable) функцию, которая может рисовать разные графики опираясь на функцию, переданную ей аргументом. Напомню, что это псевдокод, он не будет работать в настоящем JavaScript.

А теперь рассмотрим несколько настоящих примеров, сначала в JavaScript, потом в C#.

В первом примере мы рассмотрим вариант имплементации встроенной функции массивов ‘map’ в JavaScript.

function map(array, callback) {

    var result = [];

    for(var i = 0; i < array.length; i++) {

        result.push(callback(array[i]));

    }

    return result;

}

Эта функция получает массив и функцию обратного вызова, вызывает функцию обратного вызова на каждом элементе массива и на основе возвращённых значений выдаёт новый массив возвращённых значений.

Подробно разберём эту функцию:

var result = [];

Создаётся новый массив, который затем будет возвращён.

for(var i = 0; i < array.length; i++) {

    result.push(callback(array[i]));

}

Цикл ‘for’ проходит по всему массиву и вызывает ‘callback’, передавая каждый элемент массива. Значение, возвращённое функцией обратного вызова, добавляется в массив ‘result’.

return result;

Массив ‘result’ возвращается из функции.

Теперь, когда мы рассмотрели функцию, рассмотрим примеры её использования.

function callback(value) {

    return value * 2;

}

map([1, 2, 3], callback);

// Функция возвращает [2, 4, 6]

Чтобы каждый раз не объявлять функцию для вызова ‘map’, мы можем воспользоваться анонимными функциями JavaScript:

map([1, 2, 3], function(value) {

    return value * 2;

});

// Функция возвращает [2, 4, 6]

Этот код работает так-же как и первый, но он более компактен и не объявляет лишних функций.

Рассмотрим ещё один пример использования функций обратного вызова. На сей раз это имплементация функции ‘filter’:

function filter(array, filterFunction) {

    var result = [];

    for(var i = 0; i < array.length; i++) {

        if(filterFunction(array[i])) {

            result.push(array[i]);

        }

    }

    return result;

}

Эта функция, как следует из названия, фильтрует массив на основе функции обратного вызова и возвращает новый, отфильтрованный массив. Единственное отличие функции ‘filter’ от ‘map’ эти 3 строки кода:

if(filterFunction(array[i])) {

    result.push(array[i]);

}

Здесь проверяется, если функция фильтр возвращает true, тогда элемент массива ‘array’ добавляется в массив ‘result’. Таким образом функция фильтрует массив на основе функции обратного вызова.

Рассмотрим также имплементацию функции ‘reduce’:

function reduce(array, callback) {

    var accumulator = 0;

    for(var i = 0; i < array.length; i++) {

        accumulator = callback(accumulator, array[i]);

    }

    return accumulator;

}

Эта функция берёт массив и функцию обратного вызова, вызывает функция на всех элементах массива и возвращает одно результирующее значение [5]. Пример использования:

reduce([1, 2, 3, 4], function(accumulator, currentValue) {

    return accumulator + currentValue;

});

// 1 + 2 + 3 + 4 = 10

// Функция вернёт значение 10

Разберём работу функции.

var accumulator = 0;

Эта строка создаёт переменную ‘accumulator’, которая и будет нашим результирующим значением.

accumulator = callback(accumulator, array[i]);

На этой строке мы передаём значение ‘accumulator’ и каждого элемента массива функции обратного вызова и присваиваем возвращённое значение переменной ‘accumulator’.

return accumulator;

В конце функции возвращаем значение ‘accumulator’.

Перейдём на C# и рассмотрим аналоги выше указанных функций.

Аналог функции ‘map’:

delegate int MapDelegate(int input);

int[] Map(int[] array, MapDelegate callback)

{

    int[] result = new int[array.Length];

    for(int i = 0; i < array.Length; i++)

    {

        result[i] = callback(array[i]);

    }

    return result;

}

int MapCallback(int x)

{

    return x * 2;

}

int[] array = { 1, 2, 3, 4};

Map(array, MapCallback)

// { 2, 4, 6, 8 }

Здесь используется делегаты (англ. delegates). Так как C# язык со строгой типизацией, в нём нужно типизировать всё, в том числе и функции обратного вызова. Делегаты — это специальный тип, созданный для типизации функций [10]. Они описывают тип возвращаемого функцией значения и аргументов функции. С их помощью мы можем иметь строго-типизированные функции обратного вызова в аргументах. Они похожи на указатели функций в C++ [11]. Давайте разберём части кода с делегатами.

delegate int MapDelegate(int input);

Эта строка кода объявляет делегат, который инкапсулирует функцию, которая возвращает значение типа ‘int’ и получает один аргумент также типа ‘int’.

int[] Map(int[] array, MapDelegate callback)

Эта строка объявляет функцию ‘Map’, которая возвращает массив типа ‘int’ и получает два аргумента: массив типа ‘int’ и функцию типа ‘MapDelegate’.

result[i] = callback(array[i]);

Эта строка присваивает элементу массива значение, возвращённое делегатом ‘callback’.

Теперь рассмотрим другие различия версии C#.

int[] result = new int[array.Length];

На этой строке создаётся массив ‘result’, длинна которого равна длине массива ‘array’, так как функция ‘Map’ может менять значения, но не длину массива.

Теперь рассмотрим аналог функции ‘filter’․

using System.Collections.Generic;

delegate bool FilterDelegate(int input);

int[] Filter(int[] array, FilterDelegate callback)

{

    List<int> result = new List<int>();

    for(int i = 0; i < array.Length; i++)

    {

        if(callback(array[i]))

        {

            result.Add(array[i]);

        }

    }

    return result.ToArray();

}

bool FilterCallback(int x)

{

    return x > 3;

}

int[] array = { 1, 2, 3, 4, 5, 6};

Filter(array, FilterCallback); // { 4, 5, 6 }

Здесь основное отличие от JS версии – использование класса ‘List’ (не считая использование делегатов). Класс ‘List’ здесь использован так как в C# массивы не динамичны, а мы не можем точно знать размер нового массива после фильтрации. ‘List’ абстрагирует аллокацию памяти и работу с массивами, что мы используем для того, чтобы динамически добавлять элементы в массив [12].

Рассмотрим код, который взаимодействует с ‘List’.

using System.Collections.Generic;

‘System.Collections.Generic’ – пространство имен, содержащее класс ‘List’, используем оператор using что бы задействовать его в нашей программе.

List<int> result = new List<int>();

Эта строка инициализирует новый объект типа ‘List’. ‘<int>’ обозначает универсальный шаблон. Универсальные шаблоны – это техника, которая позволяет передать тип классу или функции как аргумент [13]. В данном случае используя универсальный шаблон мы обозначаем что наш лист будет нести в себе значения типа ‘int’. Универсальные шаблоны– очень удобно, когда есть один код, который работает между разными типами. Эта техника позволяет не переписывать новый класс или функцию для каждого типа.

result.Add(array[i]);

Эта строка динамически добавляет новый элемент в массив ‘result’, используя функцию ‘Add’ класса ‘List’.

return result.ToArray();

Эта строка конвертирует объект ‘List’ в массив типа ‘int’ и возвращает его из функции. Таким образом мы получаем чистый массив из функции.

Также рассмотрим аналог функции ‘reduce’:

delegate int ReduceDelegate(int accumulator, int value);

int Reduce(int[] array, ReduceDelegate reducer)

{

    int accumulator = 0;

    for(int i = 0; i < array.Length; i++)

    {

        accumulator = reducer(accumulator, array[i]);

    }

    return accumulator;

}

int Reducer(int accumulator, int value)

{

    return accumulator + value;

}

int[] array = { 1, 2, 3, 4 };

Reduce(array, Reducer); // 10

Вывод 2

Рекурсия – техника вызова функцией самой себя. Она позволяет упростить решения алгоритмов, которые можно разделить на аналогичные простые задачи. Обычно алгоритмы с использованием рекурсии можно так же решить с помощью циклов.

Функции обратного вызова – концепция позволяющая использовать функции как аргументы. Она позволяет создавать высоко настраиваемые функции, которые берут часть своей логики из аргументов. Функции обратного вызова так-же позволяют выполнять данные функции после выполнения определённых операций.

Эти две техники позволяют использовать функции эффективнее, а также упрощают и укорачивают код.

Глава 3

Асинхронные функции

3.1 Теория

Часто в программировании часто требуется производить задачи, затрачивающие немалое время. Например, отправление HTTP запроса. В синхронной модели программирования программа должна была бы ждать, пока запрос не завершился бы, а потом уже продолжать выполнение. В асинхронной же модели программа может выполнять другие процессы, пока запрос не закончен [14]. Большинство современных языков программирование абстрагируют и упрощают асинхронное программирование до использования нескольких ключевых слов в функциях. Таким образом можно объявлять асинхронные функции, которые могут выполнятся параллельно.

Синтаксис асинхронного программирования очень похож в C# и JavaScript. В обоих языках используются ключевые слова ‘async’ и ‘await’, на которых и построено все асинхронные концепты [15]. Ключевое слово ‘async’ используется для объявления асинхронных функций, а ‘await’ для того, чтобы подождать пока асинхронная операция завершилась. ‘await’ может использоваться только в асинхронной функции.

3.2 Асинхронные функции в JavaScript

Асинхронное программирование в JavaScript строится на классе ‘Promise’. Один объект Promise – одна асинхронная операция в JavaScript [16].

Все асинхронные функции автоматически возвращают объекты Promise. Объекты Promise имеют два способа завершения: resolve и reject. Resolve – это когда операция завершена успешно, Reject – когда операция провалилась или завершилась ошибкой. Для создания Promise мы должны передать конструктору Promise функцию, которая получает два аргумента – Resolve и Reject, которые тоже являются функциями. С помощью этих функций мы можем завершить операцию с соответствующим результатом.

Рассмотрим пример использования:

const promise = new Promise(resolve => {

    setTimeout(resolve, 500);

});

Здесь создаётся Promise, который через 500 миллисекунд завершится успешно.

const promise = new Promise(reject => {

    setTimeout(reject, 500);

});

Это тот же Promise, однако этот завершается провалом.

Теперь, когда мы разобрались с ‘Promise’, рассмотрим сами асинхронные функции.

const sleep = time => new Promise(resolve => 

setTimeout(resolve, time)

);

async function asyncExample() {

    console.log("Сейчас"); 

    await sleep(5000); // 5000 миллисекунд = 5 секунд

    console.log("Прошло 5 секунд");

}

asyncExample();

В этом примере мы сначала создаём функцию ‘sleep’, которая принимает время и возвращает Promise завершающийся через данное время. Затем мы создаём асинхронную функцию ‘asyncExample’, в которой используем функцию ‘sleep’ с помощью ключевого слова ‘await’.

Рассмотрим ещё одни пример:

async function getData() {

    // Получить данные

    const response = await fetch("api.myproject.com/getdata");

    // Конвертировать в обьект JSON

    const data = await response.json();

    return data;

}

async function useData() {

    const data = await getData();

    // Работа с data-ой

}

В этом примере демонстрируется использование функции стандартной библиотеки ‘fetch’, эта функция используется для выполнения HTTP запросов в браузерном JavaScript [17]. В данном примере мы эффективно используем async и await для выполнения асинхронных операций.

3.3 Асинхронные функции в C#

В C# Аналог класса Promise – класс Task. Все асинхронные функции в C# имеют тип возвращаемого значения Task [18]. Рассмотрим пример.

using System.Threading.Tasks;

async Task sleep(int time)

{

    await Task.Delay(time);

}

async Task Main(string[] args)

{

    Console.WriteLine("Сейчас");

    await sleep(5000);

    Console.WriteLine("Прошло 5 секунд");

}

‘System.Threading.Tasks’ – пространство имён содержащее тип ‘Task’. В этом примере мы создаём асинхронную функцию ‘sleep’, где используем статическую функцию ‘Task.Delay’. Затем, в асинхронной функции ‘Main’, мы используем функцию ‘sleep’ оператором ‘await’.

Рассмотрим ещё один пример:

using System.Net.Http;

using System.Threading.Tasks;

HttpClient client = new HttpClient();

async Task Main()

{

    try

    {

        string responseBody = await client.GetStringAsync("http://google.com");

        Console.WriteLine(responseBody);

    }

    catch (HttpRequestException)

    {

        Console.WriteLine("Ошибка, запрос провалился");

    }

[19]

Здесь мы совершаем HTTP запрос и пишем ответ в консоль используя, асинхронные функции. Разберём код.

using System.Net.Http;

Используем это пространство имён для работы с HTTP.

HttpClient client = new HttpClient();

Создаём новый HTTP клиент для выполнения запроса.

string responseBody = await client.GetStringAsync(

"http://google.com");

Console.WriteLine(responseBody);

Получаем HTTP ответ, используя асинхронную функцию ‘client.GetStringAsync’.

Вывод 3

Асинхронные функции в программировании позволяют легко имплементировать асинхронную модель. В некоторых случаях асинхронные функции могут сохранить не малое время для исполнения задач. Они особо полезны, когда нужно выполнять несколько задач параллельно.

Синтаксис асинхронных функция очень похож в JavaScript и C#. В обоих

языках присевает абстрактный класс для асинхронных задач – Promise в JavaScript и Task в C#.

Заключение

Как было сказано раньше, функции – одна из главных концепций в программировании. В этой работе мы рассмотрели фундаментальные правила и концепции функций в JavaScript и C#, практические примеры их использования, асинхронные функции, а также техники как рекурсия и функции обратного вызова.

Библиография

1. Eloquent JavaScript, 3rd Edition: A Modern Introduction to Programming. Marijn Haverbeke. 2018. – 472p.

  1. https://eloquentjavascript.net/03_functions.html#p_H5CjsrL2Dh

  2. https://eloquentjavascript.net/03_functions.html#h_tqLFw/oazr

  3. https://eloquentjavascript.net/03_functions.html#h_jxl1p970Fy

  4. https://eloquentjavascript.net/03_functions.html#h_XqQR5FlX+8

  5. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Defining_functions

  6. https://eloquentjavascript.net/03_functions.html#p_H5CjsrL2Dh

  7. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/methods

  8. https://eloquentjavascript.net/03_functions.html#h_jxl1p970Fy

  9. https://eloquentjavascript.net/11_async.html#h_n9ws/jdPpb

  10. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/

  11. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/#delegates-overview

  12. https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netframework-4.8

  13. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/

  14. https://eloquentjavascript.net/11_async.html#p_UMfVhtpDiH

  15. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/methods#async-methods

  16. https://eloquentjavascript.net/11_async.html#h_sdRy5CTAP/

  17. https://eloquentjavascript.net/18_http.html#h_1Iqv5okrKE

  18. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/methods#async-methods

  19. https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netframework-4.8