10 минут на понимание асинхронного программирования JS (от концепции к принципу)

JavaScript

Введение в асинхронное программирование

   Хорошо известно, что среда выполнения языка JavaScript является «однопоточной» (один поток, что означает, что за один раз может быть выполнена только одна задача, если задач несколько, вы должны ждать в очереди, предыдущая задача выполняется, а затем выполняется следующая задача). Этот «однопоточный» режим менее эффективен и требует много времени для выполнения задачи.
   Чтобы решить эту проблему, «асинхронный режим» (Асинхронный режим, что означает, что последняя задача выполняется, не дожидаясь выполнения предыдущей задачи, и каждая задача имеет одну или несколько функций обратного вызова).
  Асинхронный режим делает JavaScript очень эффективным при обработке транзакций, но он также создает множество проблем, таких как сложная обработка исключений и глубокая вложенность.

Во-вторых, развитие асинхронного программирования

Асинхронное программирование JavaScript постоянно развивалось и совершенствовалось с момента его появления и в основном прошло следующие четыре стадии:

Этап 1 Традиционная функция обратного вызова

Функция обратного вызова    заключается в том, чтобы записать вторую часть задачи в отдельную функцию, и при повторном выполнении задачи функция вызывается напрямую. Его английское название callback буквально переводится как «отзыв». Хотя обратные вызовы в основном используются в асинхронном программировании, методы с обратными вызовами не обязательно являются асинхронными.

// demo1(简单callback封装)
function successCallback() {
    console.log('callback');
}
function fn(successCallback) {
    console.log('这里表示执行了一大堆各种代码');

    // 其他代码执行完毕,最后执行回调函数
   successCallback instanceof Function && successCallback();
}
fn(successCallback);
// demo2(回调地域)

//callback hell
doSomethingAsync1(function(){
    doSomethingAsync2(function(){
        doSomethingAsync3(function(){
            doSomethingAsync4(function(){
                doSomethingAsync5(function(){
                    // code...
                });
            });
        });
    });
});

Проблема может быть обнаружена. Когда уровень вложенности функций обратного вызова не является глубоким, код легко понять и поддерживать. После углубления уровня вложенности появится проблема «пирамиды обратного вызова», как и demo2, если это Если каждая функция обратного вызова внутри содержит много бизнес-логики, весь блок кода станет очень сложным. С точки зрения логической корректности, с написанием вышеперечисленных callback-функций проблем нет, но с увеличением и усложнением бизнес-логики недостатки этого метода написания будут обнажаться сразу, и поддерживать их действительно сложно. Слишком много боли, это называется "ад обратного вызова".

Фаза 2  Шаблон публикации/подписки на событие

  Режим публикации-подписки, определяющий связь "один ко многим", которая позволяет нескольким объектам-наблюдателям отслеживать события в объекте темы. При изменении объекта темы все зависимые объекты будут уведомлены.

var PubSub = function(){
    this.handlers = {}; 
};
PubSub.prototype.subscribe = function(eventType, handler) {
    if (!(eventType in this.handlers)) {
        this.handlers[eventType] = [];
    }
    this.handlers[eventType].push(handler); //添加事件监听器
    return this;//返回上下文环境以实现链式调用
};
PubSub.prototype.publish = function(eventType) {
    var _args = Array.prototype.slice.call(arguments, 1);
    for (var i = 0, _handlers = this.handlers[eventType]; i < _handlers.length; i++) {
        _handlers[i].apply(this, _args);// 遍历事件监听器
    }
    return this;
};
var event = new PubSub;// 构造PubSub实例
event.subscribe('list', function(msg) {
    console.log(msg);
});
event.publish('list', {data: ['one,', 'two']});// Object {data: Array[2]}

Этап 3 Отложен

  jQuery $.Deferred() — это конструктор, который возвращает цепочку методов служебного объекта для регистрации нескольких обратных вызовов и вызова очереди обратных вызовов, передавая статус успешного или неудачного выполнения любой синхронной или асинхронной функции. Deferred.resolve используется в успешном обратном вызове done(), а deferred.reject используется в ошибочном обратном вызове fail().

var deferred = $.Deferred();
$.ajax(url, {
    type: "post",
    dataType: "json",
    data: data
}).done(function(json) [
    if (json.code !== 0) {
        showError(json.message || "操作发生错误");
        deferred.reject();
    } else {
        deferred.resolve(json);
    }
}).fail(function() {
    showError("服务器错误,请稍后再试");
    deferred.reject();
}).always(function() {
    if (button) {
        button.prop("disabled", false);
    }
});
return deferred.promise();

Этап 4 Обещание

  Promise— это решение для асинхронного программирования, более разумное и мощное, чем традиционные решения — функции обратного вызова и события. Впервые он был предложен и реализован сообществом, ES6 вписал его в стандарт языка, унифицированное использование и изначально предоставил объекты Promise.
   Так называемый Promise — это просто контейнер, содержащий результат события (обычно асинхронной операции), которое завершится в будущем. Синтаксически Promise — это объект, из которого можно получить сообщение для асинхронной операции. Обещания предоставляют унифицированный API, и различные асинхронные операции могут обрабатываться одинаково.
   Используя объект Promise, вы можете выражать асинхронные операции в процессе синхронных операций, избегая слоев вложенных асинхронных обратных вызовов, а код становится более понятным и понятным, простым в обслуживании, а также может перехватывать исключения.

function fn(num) {
  return new Promise(function(resolve, reject) {
    if (typeof num == 'number') {
      resolve();
    } else {
      reject();
    }
  })
  .then(function() {
    console.log('第1个then:参数是一个number值');
  })
  .then(null, function() {
    console.log('第2个then');
  })
}
fn('haha');
fn(1234);// 第1个then:参数是一个number值、第2个then

Плюсы и минусы решений асинхронного программирования:

обратный вызовобратный вызов Опубликовать/подписаться на событие Deferred Promise
преимущество Просто и понятно Легко понять, можно связать несколько событий, и для каждого события можно указать несколько функций обратного вызова. Он избегает вложенных функций обратного вызова и имеет унифицированный API, что упрощает управление асинхронными операциями. ES6 записывает промисы в стандарт языка, унифицирует используемый синтаксис, лаконичен и удобен в использовании, более разумен и мощен, чем традиционные асинхронные решения.
недостаток Это не способствует чтению и обслуживанию кода, будет «область обратного вызова», и каждая задача может указать только одну функцию обратного вызова. Движимый событиями, процесс операции становится очень неясным Состояние необратимо.После определения состояния повторный вызов разрешения/отклонения не влияет на исходное состояние. состояние необратимо

В-третьих, механизм реализации асинхронного программирования

Мы глубоко поймем механизм реализации асинхронного программирования из следующих 3 шагов:

1. Создайте контейнер для хранения нескольких обратных вызовов

/**
 * 1.创建容器list
 * 2.依次执行
 */
(function(root){
    var _ = {
    	callbacks: function(options) {
            var list = [];
            var index, length;
            // 依次执行
            var fire = function(data) {
            	index = 0;
            	length = list.length;
            	for (; index < length; index++) {
                    list[index].apply(data[0], data[1]);
            	}
            }
            var self = {
                add: function() {
                    var args = [].slice.call(arguments);
                    args.forEach(function(fn){
                    	if(toString.call(fn) == '[object Function]') {
                    		list.push(fn);
                    	}
                    })
                },
                fireWith: function(context, arguments) {
                    var args = [context, arguments];
                    fire(args)
                },
                // 传参
                fire: function() {
                    self.fireWith(this, arguments)
                }
            }
            return self;
    	}
    }
    root._ = _;
})(this);

// 调用
var callList = _.callbacks();
callList.add(function(){
    console.log('1111')
})
callList.add(function(){
    console.log('2222')
})
callList.fire();

2. Контейнер добавляет контроль состояния (stopFalse, один раз, память)

1) stopFalse, когда функция обратного вызова выполняется, а выполнение останавливается позже

var fire = function(data) {
    index = 0;
    length = list.length;
    for (; index < length; index++) {
    	if(list[index].apply(data[0], data[1]) === false && options.stopFalse) {// 配置了stopFalse状态,且有回调函数返回false,终止后面执行
    	    break;
    	}
    }
}

// 调用
var callList = _.callbacks('stopFalse');
callList.add(function(){
    console.log('1111');
    return false;
});
callList.add(function(){
    console.log('2222')
});
callList.fire();// 111

2) После того, как только выполняет только одно состояние

fireWith: function(context, arguments) {
    var args = [context, arguments];
    // 第一次调fire:false || true
    // 调用fire后就将testing设为true,!testing就为false
    if(!options.once || !testing) {
    	fire(args)
    }
}

// 调用
var callList = _.callbacks('once');
callList.add(function(){
    console.log('1111');
});
callList.add(function(){
    console.log('2222')
});
callList.fire();// 111、222,只执行一次fire
callList.fire();

3) память запоминает состояние последнего выполнения

(function(root){
    var _ = {
        callbacks: function(options) {
            options = typeof options === 'string' ? createOptions(options) : {};
            
            var list = [];
            var index, length, testing, memory, start, starts;
            
            // 依次执行
            var fire = function(data) {
            	console.log(data)
            	memory = options.memory && data;
            	testing = true;
            	index = starts || 0;
            	starts = 0;
            	length = list.length;
            	for (; index < length; index++) {
            		if(list[index].apply(data[0], data[1]) === false && options.stopFalse) {
            			break;
            		}
            	}
            }
            var self = {
            	add: function() {
                    var args = [].slice.call(arguments);
                    start = list.length;
                    args.forEach(function(fn){
                    	if(toString.call(fn) == '[object Function]') {
                            list.push(fn);
                    	}
                    })
                    // 重新调用fire,执行callback
                    if(memory) {
                    	starts = start
                    	fire(memory)
                    }
            	},
            	fireWith: function(context, arguments) {
                    var args = [context, arguments];
                    // 第一次调fire:false || true
                    if(!options.once || !testing) {
                    	fire(args)
                    }
            	},
            	// 传参
            	fire: function() {
                    self.fireWith(this, arguments)
            	}
            }
            return self;
        }
    }
    function createOptions(options) {
    	var obj = {};
    	options.split(/\s+/).forEach(function(value){
    		obj[value] = true;
    	})
    	return obj;
    }
    root._ = _;
})(this);

// 调用
var callList = _.callbacks('memory');
callList.add(function(){
    console.log('1111');
});
callList.add(function(){
    console.log('2222')
});
callList.fire(); // 1111、2222
callList.add(function(){
    console.log('memory')
}); // memory

3, разрешать, отклонять, уведомлять и другие синтаксические инкапсуляции сахара

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

Код реализации:

Deferred: function() {
    // 创建语法糖的过程
    var tuples = [
    	[ "resolve", "done", _.callbacks("once memory"), "resolved" ],
    	[ "reject", "fail", _.callbacks("once memory"), "rejected" ],
    	[ "notify", "progress", _.callbacks("memory") ]
    ]
    
    var promise = {}; // 添加数据(回调)
    var deferred = {}; // 状态控制
    var state; // 最终状态
    tuples.forEach(function(tuple, i) {
    	var callList = tuple[2],
    		stateString = tuple[3];
    	// promise[ done | fail | progress] = callList.add
    	promise[tuple[1]] = callList.add;
    
    	// Handle state
    	if(stateString) {
    		// state = [ resolved | rejected ]
    		state = stateString;
    	}
    
    	// deferred[ resolve | reject | notify] = callList.fire
    	deferred[tuple[0]] = function() {
    		deferred[tuple[0] + 'With'](this === deferred ? promise : this, arguments);
    		return this;
    	}
    	deferred[tuple[0] + 'With'] = callList.fireWith;
    	
    	return deferred;
    })
}

резюме

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