Асинхронное решение на JS

внешний интерфейс JavaScript Promise
Асинхронное решение на JS

callback

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

Example

Определяется следующим образомdelayфункция,callbackТип параметра — функция, которая будет выполнена через 1 секунду.

function delay(callback) {
    console.log('foo');
    setTimeout(callback, 1000);
}

delay(console.log.bind(console, 'bar')); 

// 控制台立即打印 foo
// 1秒钟后控制台输出 bar

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

  • Сценарий 1. Предположим, что все задержки A, delayB и delayC являются асинхронными операциями.callbackПараметры выполняются после завершения операции.Выполненная операция зависит от результата операции delayC, операция delayC зависит от delayB, а delayB зависит от delayA.Как мы это решаем? :
function delayA(callback) {
    setTimeout(() => {
        callback(1);
    }, 1000);
}

function delayB(fn, somthingFromA) {
    setTimeout(() => {
        callback(somthingFromA + 1);
    }, 2000);
}

function delayC(fn, somthingFromB) {
    setTimeout(() => {
        callback(somthingFromB + 1);
    }, 3000);
}

function done(result) {
    console.log(result);
}

delayA((feedBackByA) => {
    delayB((feedBackByB) => {
        delayC((feedBackByC) => {
            done(feedBackByC);
        }, feedBackByB);
    }, feedBackByA);
});
// 6 秒后控制台输出 3
  • Сценарий 2: выполняемая операция зависит от результатов операций delayA, delayB и delayC, но между задержкой A, задержкой B и задержкой C нет никакой зависимости. Они определяются следующим образом:
function delayA(callback) {
    setTimeout(() => {
        callback(1);
    }, 1000);
}

function delayB(fn) {
    setTimeout(() => {
        callback(2);
    }, 2000);
}

function delayC(fn) {
    setTimeout(() => {
        callback(3);
    }, 3000);
}

function done(somthingFromA, somthingFromB, somthingFromC) {
    console.log(somthingFromA, somthingFromB, somthingFromC);
}

Конечно, мы можем использовать то же решение в сценарии 1, но синхронное выполнение нескольких несвязанных асинхронных операций задерживает время выполнения выполненной операции.

delayA((feedBackByA) => {
   delayB((feedBackByB) => {
       delayC((feedBackByC) => {
           done(feedBackByA, feedBackByB, feedBackByC);
       });
   });
});
// 6 秒后控制台输出 1, 2, 3

Следовательно, необходимо определить вспомогательную функцию, которая вызывает одновременное выполнение нескольких асинхронных операций и выполняет функцию обратного вызова, когда завершается асинхронная операция с последним обратным вызовом:

/*
* fns: 异步操作函数的数组
* callback: 所有异步操作都完成时的回调
*/
var helper = (fns, callback) => {
    // 定义一个 checkList,标识每个异步操作是否完成
    const checkList = new Array(fns.length).fill(false);
    // 保存每个异步操作返回的参数供 callback 使用
    const parameters = new Array(fns.length);

    // 判断是否所有的异步操作都已完成
    function allCheck() {
        return checkList.reduce((prev, val) => {
            return prev && val;
        }, true);
    }
    
    fns.forEach((fn, index) => {
        fns((feedBack) => {
            parameters[index] = feedBack;
            checkList[index] = true;
            if (allCheck()) {
                callback.apply(null, parameters);
            }
        });
    });
};

helper([delayA, delayB, delayC], done);
// 3 秒后控制台输出 1, 2, 3

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

Promise

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

Студенты, которые не знают Promise, могут сначала посмотретьdeveloper.Mozilla.org/en-US/docs/…

Во-первых, давайте посмотрим на преобразование предыдущей асинхронной функции в промис:

funcion promisify(fn) {
    return (args) => 
        return new Promise((resolve, reject) => {
            // fn 是一个异步操作,第一参数是回调函数,第二个参数是执行的参数
            fn(resolve, args);
        });
    };
}

Таким образом, исходная асинхронная операция с использованием обратного вызова в качестве параметра выглядит следующим образом:

function delay(callback, content) {
    setTimeout(() => callback(content), 1000);
}

может быть обещано:

const delayPromise = promisify(delay);

delayPromise('foo').then((content) => {
    console.log(content);
});
// 1 秒后控制台输出 foo

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

promisify(delayA)()
    .then((feedbackFromA) => {
        return promisify(delayB)(feedbackFromA);
    })
    .then((feedbackFromB) => {
        return promisify(delayC)(feedbackFromB);
    })
    .then((feedbackFromC) => {
        done(feedbackFromC);
    });
// 6 秒后控制台输出 3

Посмотрите на вторую сцену:

Promise.all([
    promisify(delayA)(),
    promisify(delayB)(),
    promisify(delayC)()
]).then(([feedbackFromA, feedbackFromB, feedbackFromC]) => {
    done(feedbackFromA, feedbackFromB, feedbackFromC);
});
// 3 秒后控制台打印 1,2,3

Как только все асинхронные операции будут инкапсулированы как объекты Promise, очевидно, что больше не будет проблем с адскими обратными вызовами.

Generator

GeneratorТакже концепция, представленная в ES6. дляGeneratorДля студентов, которые не знают, сначала проверьте документdeveloper.Mozilla.org/en-US/docs/…

Сначала давайте посмотрим наGeneratorКак это работает:

function* main() {
    const a = yield 1; // a 为第二次调用 next 时传入的参数
    const b = yield 2 + a; // b 为第三次调用 next 时传入的参数
    yield 3 + b;
    return 4;
}
const gen = main();
gen.next(); // {value: 1, done: false}
gen.next(1); // {value: 3, done: false}
gen.next(5); // {value: 8, done: false}
gen.next(); // {value: 4, done: true}

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

helper(function* main(args) {
    const feedBackFromA = yield promisify(delayA)(args);
    const feedBackFromB = yield promisify(delayB)(feedBackFromA);
    const feedBackFromC = yield promisify(delayC)(feedBackFromB);
    done(feedBackFromC);
})(realArgs);

этоhelperФункция должна удовлетворять следующим условиям:

  • автоматическое выполнениеgeneratorдоgeneratorстатус завершен
  • yieldпосле ключевого словаPromiseЗначение разрешения возвращается как параметр для следующего вызова next.
  • Обещание возврата, используйтеgeneratorВозвращаемое значение экземпляра используется как значение разрешения

Поэтому определите помощника следующим образом:

function helper(genFn) {
    return (...args) => new Promise(resolve, reject) => {
        let gen = genFn(args);
        function next(prev) => {
            const {
                value,
                done
            } = gen.next(prev);
            if(done) {
                return resolve(next.value);
            } else {
                return value.then(next);
            }
        }
        next();
    });
}

Так

helper(main)();
// 6 秒后控制台打印 3

Тогда проблема сценария 2 может быть решена с помощью этого помощника:

 helper(function* main() {
    const [feedBackFromA, feedBackFromB, feedBackFromC] = yield Promise.all([
        promisify(delayA)();
        promisify(delayB)();
        promisify(delayC)();
    ]);
    d(feedBackFromA, feedBackFromB, feedBackFromC);
})();
// 3 秒后控制台打印 1,2,3

Желающие могут обработать это самостоятельноhelper, чтобы он поддерживал следующий синтаксис:

helper(function* main() {
    const [feedBackFromA, feedBackFromB, feedBackFromC] = yield [
        promisify(delayA)();
        promisify(delayB)();
        promisify(delayC)();
    ];
    d(feedBackFromA, feedBackFromB, feedBackFromC);
})();

Вот хорошая сторонняя библиотека, рекомендуемаяco, что дает очень мощныйhelperреализовать егоyieldПозже он может принимать Array, Promise, Function и многие другие типы.

Async Function

Нативная поддержка Async-функций была добавлена ​​в ES7.

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

async function main() {
    const feedBackFromA = await promisify(delayA)();
    const feedBackFromB = await promisify(delayB)(feedBackFromA);
    const feedBackFromC = await promisify(delayC)(feedBackFromB);
    d(feedBackFromC);
}

Сценарий второй:

async function main() {
    const feedBacks = await Promise.all([
        promisify(delayA)(/* a的参数 */);
        promisify(delayB)(/* b的参数 */);
        promisify(delayC)(/* c的参数 */);
    ]);
    d(feedBacks);
}

обработка ошибок

callback

Обрабатывать ошибки в соответствующих обратных вызовах.

a(() => {
    if (err) {
        // handle the error of a
    }
    b(() => {
        if (err) {
            // handle the error of b
        }
        ...
    });
});

Promise

Используйте метод catch, чтобы получить унифицированную обработку ошибок во всем процессе Promise, или обрабатывайте их по отдельности во втором параметре метода then.

promisify(delayA)()
    .then(promisify(delayB), (err) => {
        // handle the error of a
    })
    .catch((err) => {
        // handle all errors
    });

Генераторы и асинхронные функции

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

async function main() {
    try {
        const feedBackFromA = await promisify(delayA)(/* a的参数 */);
        const feedBackFromB = await promisify(delayB)(aRes);
        const feedBackFromC = await promisify(delayC)(feedBackFromB);
        d(feedBackFromC);
    } catch (err) {
        // handle all errors
    }
}
知识共享许可协议
В этой работе используетсяCreative Commons Attribution-NonCommercial-ShareAlike 4.0 Международная лицензияЛицензия.