Подробно объясните процесс разработки асинхронного JS.

внешний интерфейс JavaScript

Чтобы понять почему, сначала поймите три понятия:

1. Что такое синхронизация?

Так называемая синхронизация означает, что при поступлении «вызова» «вызов» не возвращается до тех пор, пока не будет получен результат. Но как только вызов возвращается, вы получаете возвращаемое значение. Другими словами, «вызывающий» активно ожидает результата «вызова». Блокирует выполнение кода после завершения этого вызова.

2. Что такое асинхронность?

После выполнения «вызова» вызов возвращается напрямую, поэтому результат не возвращается. Другими словами, при вызове асинхронной процедуры вызывающая сторона не получает результат немедленно. Вместо этого после выполнения «вызова» «вызываемый» уведомляет вызывающего абонента через статус, уведомление или обрабатывает вызов с помощью функции обратного вызова. После выполнения асинхронного вызова он не влияет на выполнение следующего кода.

3. Почему вам нужен асинхронный в JavaScript?

Во-первых, мы знаем, что JavaScript однопоточный (даже если добавить webworker, но по сути однопоточный или JS). Код синхронизации Что это значит? Означает, что есть вероятность засорения, когда долго у нас задача требует, если использовать синхронный режим, то он выполнит код после блокировки. Асинхронного нет, значит асинхронный код ждать не будем, код продолжается после асинхронного задания.

Другие статьи можно нажать: GitHub.com/Иветт Л.А. Ю/Б…

Поняв концепцию, мы собираемся перейти к сегодняшней теме. Прежде всего, давайте задумаемся: какие асинхронные решения в основном используются в работе, и каковы преимущества и недостатки этих асинхронных решений?

Самое раннее решение асинхронности — это функции обратного вызова, такие как обратные вызовы событий, обратные вызовы в setInterval/setTimeout. Но есть очень распространенная проблема с функциями обратного вызова, а именно проблема ада обратных вызовов (пример будет объяснен позже);

Чтобы решить проблему ада обратных вызовов, сообщество предложило решение Promise, которое было прописано в стандарте языка ES6. Promise в определенной степени решает проблему ада обратных вызовов, но есть и некоторые проблемы с Promise, такие как ошибки, которые нельзя отловить с помощью try catch, а цепной вызов Promise принципиально не решает проблему ада обратных вызовов.

Функция Generator представлена ​​в ES6. Генератор — это решение для асинхронного программирования. Функция Generator — это реализация сопрограмм в ES6. Самая большая особенность заключается в том, что она может передавать право выполнения функции. Места отмечены оператором yield. Но Генераторы более сложны в использовании.

ES7 предложил новое асинхронное решение: async/await, async — это синтаксический сахар функции Generator, async/await делает асинхронный код похожим на синхронный, цель разработки асинхронного программирования — сделать так, чтобы асинхронный логический код выглядел как синхронный.

Функция обратного вызова ---> Promise ---> Генератор ---> async/await.

1.callback

//node读取文件
fs.readFile(xxx, 'utf-8', function(err, data) {
    //code
});

Сценарии использования функции обратного вызова (включая, но не ограничиваясь):

  1. обратный вызов события
  2. Node API
  3. Функция обратного вызова в setTimeout/setInterval
  4. ajax-запрос

Преимущества callback-функций: Простота.

Недостатки функции обратного вызова:

Вложение асинхронных обратных вызовов усложнит поддержку кода, а единообразная обработка ошибок неудобна.try catchИ ад обратного вызова (например, сначала чтение текстового содержимого A, затем чтение B в соответствии с текстовым содержимым A, а затем чтение C в соответствии с содержимым B...).

fs.readFile(A, 'utf-8', function(err, data) {
    fs.readFile(B, 'utf-8', function(err, data) {
        fs.readFile(C, 'utf-8', function(err, data) {
            fs.readFile(D, 'utf-8', function(err, data) {
                //....
            });
        });
    });
});

2.Promise

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

Затем давайте посмотрим, как Promise решает проблему ада обратных вызовов, все еще взяв в качестве примера вышеописанный файл readFile (сначала прочитайте текстовое содержимое A, затем прочитайте B в соответствии с текстовым содержимым A, а затем прочитайте C в соответствии с содержимым B) .

function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}
read(A).then(data => {
    return read(B);
}).then(data => {
    return read(C);
}).then(data => {
    return read(D);
}).catch(reason => {
    console.log(reason);
});

Преимущества обещаний:

  1. Как только состояние изменится, оно больше не изменится, вы можете получить этот результат в любое время.
  2. Асинхронные операции могут быть выражены в потоке синхронных операций, избегая уровней вложенных функций обратного вызова.

недостаток:

  1. Невозможно отменить обещание
  2. Находясь в состоянии ожидания, невозможно узнать, к какому этапу он переходит в данный момент.
  3. ошибка не может бытьtry catch

Предположим, есть такое требование: прочитать содержимое трех файлов A, B и C и вывести окончательный результат после того, как все они будут успешно прочитаны. До промисов мы обычно можем использовать модель публикации-подписки для достижения:

let pubsub = {
    arry: [],
    emit() {
        this.arry.forEach(fn => fn());
    },
    on(fn) {
        this.arry.push(fn);
    }
}

let data = [];
pubsub.on(() => {
    if(data.length === 3) {
        console.log(data);
    }
});
fs.readFile(A, 'utf-8', (err, value) => {
    data.push(value);
    pubsub.emit();
});
fs.readFile(B, 'utf-8', (err, value) => {
    data.push(value);
    pubsub.emit();
});
fs.readFile(C, 'utf-8', (err, value) => {
    data.push(value);
    pubsub.emit();
});

Обещания дают намPromise.allметод, для этого требования мы можем использоватьPromise.allреализовать.

/**
 * 将 fs.readFile 包装成promise接口
 */
function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}

/**
 * 使用 Promise
 * 
 * 通过 Promise.all 可以实现多个异步并行执行,同一时刻获取最终结果的问题
 */
Promise.all([
    read(A),
    read(B),
    read(C)
]).then(data => {
    console.log(data);
}).catch(err => console.log(err));

Исполняемый код можно штамповать:GitHub.com/Иветт Л.А. Ю/Б…

3.Generator

Функция Generator — это решение для асинхронного программирования, предоставляемое ES6. Вся функция Generator представляет собой инкапсулированную асинхронную задачу или контейнер асинхронных задач. Места, где асинхронные операции необходимо приостановить, отмечены оператором yield.

Функции-генераторы обычно используются с yield или Promise. Функция Generator возвращает итератор. Студенты, которые мало что знают о генераторах и итераторах, пожалуйста, изучите основы самостоятельно. Давайте посмотрим на простое использование генератора:

function* gen() {
    let a = yield 111;
    console.log(a);
    let b = yield 222;
    console.log(b);
    let c = yield 333;
    console.log(c);
    let d = yield 444;
    console.log(d);
}
let t = gen();
//next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
t.next(1); //第一次调用next函数时,传递的参数无效
t.next(2); //a输出2;
t.next(3); //b输出3; 
t.next(4); //c输出4;
t.next(5); //d输出5;

Для того, чтобы всем было лучше понятно, как выполняется приведенный выше код, я нарисовал картинку, соответствующую каждому следующему вызову метода:

По-прежнему возьмите приведенный выше readFile (сначала прочитайте содержимое текста A, затем прочитайте B в соответствии с содержимым текста A, а затем прочитайте C в соответствии с содержимым B) в качестве примера, используйте библиотеку Generator + co для достижения:

const fs = require('fs');
const co = require('co');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);

function* read() {
    yield readFile(A, 'utf-8');
    yield readFile(B, 'utf-8');
    yield readFile(C, 'utf-8');
    //....
}
co(read()).then(data => {
    //code
}).catch(err => {
    //code
});

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

Как этого добиться без использования библиотеки co? Можете ли вы сами написать минимальный my_co, чтобы помочь понять принцип реализации async/await? Пожалуйста, нажмите:GitHub.com/Иветт Л.А. Ю/Б…

PS: Если вы мало что знаете о Generator/yield, рекомендуется прочитать документацию по ES6.

4.async/await

Концепция async/await была введена в ES7. Async на самом деле является синтаксическим сахаром, его реализация состоит в том, чтобы обернуть функцию Generator и автоматический исполнитель (co) в функцию.

Преимущество async/await в том, что код понятен, и вы можете справиться с проблемой ада обратных вызовов, не написав множество цепочек then, таких как промисы. А ошибки можно попробовать отловить.

По-прежнему возьмите приведенный выше readFile (сначала прочитайте текстовое содержимое A, затем прочитайте B в соответствии с содержимым текста A, а затем прочитайте C в соответствии с содержимым B) в качестве примера, используйте async/await для достижения:

const fs = require('fs');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);


async function read() {
    await readFile(A, 'utf-8');
    await readFile(B, 'utf-8');
    await readFile(C, 'utf-8');
    //code
}

read().then((data) => {
    //code
}).catch(err => {
    //code
});

Используйте async/await для выполнения этого требования: прочитайте содержимое трех файлов A, B и C, а затем выведите окончательный результат после того, как все они будут успешно прочитаны.

function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}

async function readAsync() {
    let data = await Promise.all([
        read(A),
        read(B),
        read(C)
    ]);
    return data;
}

readAsync().then(data => {
    console.log(data);
});

Таким образом, историю асинхронного развития JS можно рассматривать как обратный вызов -> обещание -> генератор -> асинхронный/ожидающий. async/await делает асинхронный код похожим на синхронный, а цель разработки асинхронного программирования — сделать код с асинхронной логикой похожим на синхронный код.

Из-за моего ограниченного уровня содержание текста может быть не на 100% правильным.Если что-то не так, пожалуйста, оставьте мне сообщение, спасибо.

Справочная статья:

[1] Подробное описание процесса разработки асинхронных функций JavaScript.

[2] ES6 Promise

[3] ES6 Generator

[4] ES6 async

[5] Асинхронное программирование JavaScript

Спасибо за вашу готовность потратить свое драгоценное время на чтение этой статьи. Если эта статья дала вам небольшую помощь или вдохновение, пожалуйста, не скупитесь на лайки и звезды. Вы, безусловно, самая большая движущая сила для меня, чтобы двигаться вперед .GitHub.com/Иветт Л.А. Ю/Б…

Подпишитесь на официальный аккаунт и присоединитесь к группе технического обмена