文档章节

JavaScript异步与Promise实现

熊建刚
 熊建刚
发布于 2017/05/10 10:05
字数 5450
阅读 1022
收藏 44

前言

如果你已经对JavaScript异步有一定了解,或者已经阅读过本系列的其他两篇文章,那请继续阅读下一小节,若你还有疑惑或者想了解JavaScript异步机制与编程,可以阅读一遍这两篇文章:

回调函数

回调函数,作为JavaScript异步编程的基本单元,非常常见,你肯定对下面这类代码一点都不陌生:

	
	component.do('purchase', funcA);
	function funcA(args, callback) {
		//...
		setTimeout(function() {
			$.ajax(url, function(res) {
				if (res) {
					callback(res)
				} else {//...}
			});
		}, 300);
		funcB();
		setTimeout(function() {
			$.ajax(arg, function(res) {
				if (res) {
					callback(res);
				}
			});
		}, 400);
	}

上面这些代码,一层一层,嵌套在一起,这种代码通常称为回调地狱,无论是可读性,还是代码顺序,或者回调是否可信任,亦或是异常处理角度看,都是不尽人意的,下面做简单阐述。

顺序性

上文例子中代码funcB函数,还有两个定时器回调函数,回调内各自又有一个ajax异步请求然后在请求回调里面执行最外层传入的回调函数,对于这类代码,你是否能明确指出个回调的执行顺序呢?如果funcB函数内还有异步任务呢?,情况又如何?

假如某一天,比如几个月后,线上出了问题,我们需要跟踪异步流,找出问题所在,而跟踪这类异步流,不仅需要理清个异步任务执行顺序,还需要在众多回调函数中不断地跳跃,调试(或许你还能记得诸如funcB这些函数的作用和实现),无论是出于效率,可读性,还是出于人性化,都不希望开开发者们再经历这种痛苦。

信任问题

如上,我们调用了一个第三方支付组件的支付API,进行购买支付,正常情况发现一切运行良好,但是假如某一天,第三方组件出问题了,可能多次调用传入的回调,也可能传回错误的数据。说到底,这样的回调嵌套,控制权在第三方,对于回调函数的调用方式、时间、次数、顺序,回调函数参数,还有下一节将要介绍的异常和错误都是不可控的,因为无论如何,并不总能保证第三方是可信任的。

错误处理

关于JavaScript错误异常,初中级开发接触的可能并不多,但是其实还是有很多可以学习实践的地方,如前端异常监控系统的设计,开发和部署,并不是三言两语能阐述的,之后会继续推出相关文章。

错误堆栈

我们知道当JavaScript抛出错误或异常时,对于未捕获异常,浏览器会默认在控制台输出错误堆栈信息,如下,当test未定义时:


	function init(name) {
		test(name)
	}

	init('jh');

输出如图:

错误堆栈信息

如图中自顶向下输出红色异常堆栈信息,Uncaught表示该异常未捕获,ReferenceError表明该异常类型为引用异常,冒号后是异常的详细信息:test is not definedtest未定义;后面以at起始的行就是该异常发生处的调用堆栈。第一行说明异常发生在init函数,第二行说明init函数的调用环境,此处在控制台直接调用,即相当于在匿名函数环境内调用。

异步错误堆栈

上面例子是同步代码执行的异常,当异常发生在异步任务内时,又会如何呢?,假如把上例中代码放在一个setTimeout定时器内执行:

	function init(name) {
		test(name)
	}
	setTimeout(function A() {
		setTimeout(function() {
			init();
		}, 0);
	}, 0);

如图:

异步回调异常堆栈

可以看到,异步任务中的未捕获异常,也会在控制台输出,但是setTimeout异步任务回调函数没有出现在异常堆栈,为什么呢?这是因为当init函数执行时,setTimeout的异步回调函数不在执行栈内,而是通过事件队列调用。

JavaScript错误处理

JavaScript的异常捕获,主要有两种方式:

  • try{}catch(e){}主动捕获异常;

    try{}catch同步异常

    如上,对于同步执行大代码出现异常,try{}catch(e){}是可以捕获的,那么异步错误呢?

    try{}catch与异步异常

    如上图,我们发现,异步回调中的异常无法被主动捕获,由浏览器默认处理,输出错误信息。

  • window.onerror事件处理器,所有未捕获异常都会自动进入此事件回调

    onerror事件处理器监听异步错误

    如上图,输出了script error错误信息,同时,你也许注意到了,控制台依然打印出了错误堆栈信 息,或许你不希望用户看到这么醒目的错误提醒,那么可以使window.onerror的回调返回true即可阻止浏览器的默认错误处理行为:

    阻止浏览器的默认错误处理行为

    当然,一般不随意设置window.onerror回调,因为程序通常可能需要部署前端异常监控系统,而通常就是使用window.onerror处理器实现全局异常监控,而该事件处理器只能注册一个回调。

回调与Promise

以上我们谈到的诸多关于回调的不足,都很常见,所以必须是需要解决的,而Promise正是一种很好的解决这些问题的方式,当然,现在已经提出了比Promise更先进的异步任务处理方式,但是目前更大范围使用,兼容性更好的方式还是Promise,也是本篇要介绍的,之后会继续介绍其他处理方式。

Promises/A+

分析了一大波问题后,我们知道Promise的目标是异步管理,那么Promise到底是什么呢?

  • 异步,表示在将来某一时刻执行,那么Promise也必须可以表示一个将来值;
  • 异步任务,可能成功也可能失败,则Promise需要能完成事件,标记其状态值(这个过程即决议-resolve,下文将详细介绍);
  • 可能存在多重异步任务,即异步任务回调中有异步任务,所以Promise还需要支持可重复使用,添加异步任务(表现为顺序链式调用,注册异步任务,这些异步任务将按注册的顺序执行)。

所以,Promise是一种封装未来值的易于复用的异步任务管理机制。

为了更好的理解Promise,我们介绍一下Promises/A+,一个公开的可操作的Promises实现标准。先介绍标准规范,再去分析具体实现,更有益于理解。

Promise代表一个异步计算的最终结果。使用promise最基础的方式是使用它的then方法,该方法会注册两个回调函数,一个接收promise完成的最终值,一个接收promise被拒绝的原因。

Promises/A

你可能还会想问Promises/A是什么,和Promises/A+有什么区别。Promises/A+在Promises/A议案的基础上,更清晰阐述了一些准则,拓展覆盖了一些事实上的行为规范,同时删除了一些不足或者有问题的部分。

Promises/A+规范目前只关注如何提供一个可操作的then方法,而关于如何创建,决议promises是日后的工作。

术语

  1. promise: 指一个拥有符合规范的then方法的对象;
  2. thenable: 指一个定义了then方法的对象;
  3. 决议(resolve): 改变一个promise等待状态至已完成或被拒绝状态, 一旦决议,不再可变;
  4. 值(value): 一个任意合法的JavaScript值,包括undefined,thenable对象,promise对象;
  5. exception/error: JavaScript引擎抛出的异常/错误
  6. 拒绝原因(reject reason): 一个promise被拒绝的原因

Promise状态

一个promise只可能处于三种状态之一:

  • 等待(pending):初始状态;
  • 已完成(fulfilled):操作成功完成;
  • 被拒绝(rejected):操作失败;

这三个状态变更关系需满足以下三个条件:

  • 处于等待(pending)状态时,可以转变为已完成(fulfilled)或者被拒绝状态(rejected);
  • 处于已完成状态时,状态不可变,且需要有一个最终值;
  • 处于被拒绝状态时,状态不可变,且需要有一个拒绝原因。

then方法

一个promise必须提供一个then方法,以供访问其当前状态,或最终值或拒绝原因。

参数

该方法接收两个参数,如promise.then(onFulfilled, onRejected):

  • 两个参数均为可选,均有默认值,若不传入,则会使用默认值;
  • 两个参数必须是函数,否则会被忽略,使用默认函数;
  • onFulfilled: 在promise已完成后调用且仅调用一次该方法,该方法接受promise最终值作参数;
  • onRejected: 在promise被拒绝后调用且仅调用一次该方法,该方法接受promise拒绝原因作参数;
  • 两个函数都是异步事件的回调,符合JavaScript事件循环处理流程

返回值

该方法必须返回一个promise:


	var promise2 = promise1.then(onFulfilled, onRejected);
	// promise2依然是一个promise对象

决议过程(resolution)

决议是一个抽象操作过程,该操作接受两个输入:一个promise和一个值,可以记为;[[resolve]](promise, x),如果x是一个thenable对象,则尝试让promise参数使用x的状态值;否则,将使用x值完成传入的promise,决议过程规则如下:

  1. 如果promisex引用自同一对象,则使用一个TypeError原因拒绝此promise;
  2. x为Promise,则promise直接使用x的状态;
  3. x为对象或函数:
    1. 获取一个x.then的引用;
    2. 若获取x.then时抛出异常e,使用该e作为原因拒绝promise;
    3. 否则将该引用赋值给then;
    4. then是一个函数,就调用该函数,其作用域为x,并传递两个回调函数参数,第一个是resolvePromise,第二个是rejectPromise
      1. 若调用了resolvePromise(y),则执行resolve(promise, y);
      2. 若调用了rejectPrtomise(r),则使用原因r拒绝promise;
      3. 若多次调用,只会执行第一次调用流程,后续调用将被忽略;
      4. 若调用then抛出异常e,则:
        1. promise已决议,即调用了resolvePromiserejectPrtomise,则忽略此异常;
        2. 否则,使用原因e拒绝promise;
    5. then不是函数,则使用x值完成promise;
  4. x不是对象或函数,则使用x完成promise

自然,以上规则可能存在递归循环调用的情况,如一个promsie被一个循环的thenable对象链决议,此时自然是不行的,所以规范建议进行检测,是否存在递归调用,若存在,则以原因TypeError拒绝promise

Promise

在ES6中,JavaScript已支持Promise,一些主流浏览器也已支持该Promise功能,如Chrome,先来看一个Promsie使用实例:


	var promise = new Promise((resolve, reject) => {
		setTimeout(function() {
			resolve('完成');
		}, 10);
	});
	promise.then((msg) => {
		console.log('first messaeg: ' + msg);
	})
	promise.then((msg) => {
		console.log('second messaeg: ' + msg);
	});

输出如下:

promise实例

构造器

创建promise语法如下:


	new Promise(function(resolve, reject) {});
  • 参数

    一个函数,该函数接受两个参数:resolve函数和reject函数;当实例化Promise构造函数时,将立即调用该函数,随后返回一个Promise对象。通常,实例化时,会初始一个异步任务,在异步任务完成或失败时,调用resolve或reject函数来完成或拒绝返回的Promise对象。另外需要注意的是,若传入的函数执行抛出异常,那么这个promsie将被拒绝。

静态方法

Promise.all(iterable)

all方法接受一个或多个promsie(以数组方式传递),返回一个新promise,该promise状态取决于传入的参数中的所有promsie的状态:

  1. 当所有promise都完成是,返回的promise完成,其最终值为由所有完成promsie的最终值组成的数组;
  2. 当某一promise被拒绝时,则返回的promise被拒绝,其拒绝原因为第一个被拒绝promise的拒绝原因;
	var p1 = new Promise((resolve, reject) => {
		setTimeout(function(){
			console.log('p1决议');
			resolve('p1');
		}, 10);
	});
	var p2 = new Promise((resolve, reject) => {
		setTimeout(function(){
			console.log('p2决议');
			resolve('p2');
		}, 10);
	});
	Promise.all( [p1, p2] )
	.then((msgs) => {
		// p1和p2完成并传入最终值
		console.log(JSON.stringify(msgs));
	})
	.then((msg) => {
		console.log( msg );
	});

输出如下:

Promise.all实例

Promise.race(iterable)

race方法返回一个promise,只要传入的诸多promise中的某一个完成或被拒绝,则该promise同样完成或被拒绝,最终值或拒绝原因也与之相同。

Promise.resolve(x)

resolve方法返回一个已决议的Promsie对象:

  1. x是一个promise或thenable对象,则返回的promise对象状态同x;
  2. x不是对象或函数,则返回的promise对象以该值为完成最终值;
  3. 否则,详细过程依然按前文Promsies/A+规范中提到的规则进行。

该方法遵循Promise/A+决议规范。

Promsie.reject(reason)

返回一个使用传入的原因拒绝的Promise对象。

实例方法

Promise.prototype.then(onFulfilled, onRejected)

该方法为promsie添加完成或拒绝处理器,将返回一个新的promise,该新promise接受传入的处理器调用后的返回值进行决议;若promise未被处理,如传入的处理器不是函数,则新promise维持原来promise的状态。

我们通过两个例子介绍then方法,首先看第一个实例:


	var promise = new Promise((resolve, reject) => {
		setTimeout(function() {
			resolve('完成');
		}, 10);
	});
	promise.then((msg) => {
		console.log('first messaeg: ' + msg);
	}).then((msg) => {
		console.log('second messaeg: ' + msg);
	});

输出如下:

then实例

输出两行信息:我们发现第二个then方法接收到的最终值是undefined,为什么呢?看看第一个then方法调用后返回的promise状态如下:

then方法返回的promise

如上图,发现调用第一个then方法后,返回promise最终值为undefined,传递给第二个then的回调,如果把上面的例子稍加改动:


	var promise = new Promise((resolve, reject) => {
		setTimeout(function() {
			resolve('完成');
		}, 10);
	});
	promise.then((msg) => {
		console.log('first messaeg: ' + msg);
		return msg + '第二次';
	}).then((msg) => {
		console.log('second messaeg: ' + msg);
	});

输出如下:

promise状态

这次两个then方法的回调都接收到了最终值,正如我们前文所说,'then'方法返回一个新promise,并且该新promise根据其传入的回调执行的返回值,进行决议,而函数未明确return返回值时,默认返回的是undefined,这也是上面实例第二个then方法的回调接收undefined参数的原因。

这里使用了链式调用,我们需要明确:共产生三个promise,初始promise,两个then方法分别返回一个promise;而第一个then方法返回的新promise是第二个then方法的主体,而不是初始promise。

Promise.prototype.catch(onRejected)

该方法为promise添加拒绝回调函数,将返回一个新promise,该新promise根据回调函数执行的返回值进行决议;若promise决议为完成状态,则新promise根据其最终值进行决议。


	var promise = new Promise((resolve, reject) => {
		setTimeout(() => {
			reject('failed');
		}, 0);
	});

	var promise2 = promise.catch((reason) => {
		console.log(reason);
		return 'successed';
	});
	var promise3 = promise.catch((reason) => {
		console.log(reason);
	});
	var promise4 = promise.catch((reason) => {
		console.log(reason);
		throw 'failed 2';
	});

输出如下图:

Promise catch实例

如图中所输出内容,我们需要明白以下几点:

  1. catch会为promise注册拒绝回调函数,一旦异步操作结束,调用了reject回调函数,则依次执行注册的拒绝回调;
  2. 另外有一点和then方法相似,catch方法返回的新promise将使用其回调函数执行的返回值进行决议,如promise2,promise3状态均为完成(resolved),但是promise3最终值为undefined,而promise2最终值为successed,这是因为在调用promise.catch方法时,传入的回调没有显式的设置返回值;
  3. 对于promise4,由于调用catch方法时,回调中throw抛出异常,所以promise4状态为拒绝(rejected),拒绝原因为抛出的异常;
  4. 特别需要注意的是这里一共有四个promise,一旦决议,它们之间都是独立的,我们需要明白无论是then方法,还是catch方法,都会返回一个新promise,此新promise与初始promise相互独立。

catch方法和then方法的第二个参数一样,都是为promise注册拒绝回调。

链式调用

和jQuery的链式调用一样,Promise设计也支持链式调用,上一步的返回值作为下一步方法调用的主体:


	new Promise((resolve, reject) => {
		setTimeout(()=>{
			resolve('success');
		},0);
	}).then((msg) => {
		return 'second success';
	}).then((msg) => {
		console.log(msg);
	});

最后输出:second success,初始化promise作为主体调用第一个then方法,返回完成状态的新promise其最终值为second success,然后该新promise作为主体调用第二个then方法,该方法返回第三个promise,而且该promise最终值为undefined,若不清楚为什么,请回到关于Promise.prototype.thenPromise.prototype.catch的介绍。

错误处理

我们前文提到了JavaScript异步回调中的异常是难以处理的,而Promise对异步异常和错误的处理是比较方便的:


	var promise = new Promise((resolve, reject) => {
		test(); // 抛出异常
		resolve('success'); // 被忽略
	});
	console.log(promise);
	promise.catch((reason) => {
		console.log(reason);
	});

输出如图,执行test抛出异常,导致promise被拒绝,拒绝原因即抛出的异常,然后执行catch方法注册的拒绝回调:

promise错误处理

决议,完成与拒绝

目前为止,关于Promise是什么,我们应该有了一定的认识,这里,需要再次说明的是Promise的三个重要概念及其关系:决议(resolve),完成(fulfill),拒绝(reject)。

  1. 完成与拒绝是Promise可能处于的两种状态;
  2. 决议是一个过程,是Promise由等待状态变更为完成或拒绝状态的一个过程;
  3. 静态方法Promise.resolve描述的就是一个决议过程,而Promise构造函数,传入的回调函数的两个参数:resolve和reject,一个是完成函数,一个是拒绝函数,这里令人疑惑的是为什么这里依然使用resolve而不是fulfill,我们通过一个例子解释这个问题:

	var promise = new Promise((resolve, reject) => {
		resolve(Promise.reject('failed'));
	});
	promise.then((msg) => {
		console.log('完成:' + msg);
	}, (reason) => {
		console.log('拒绝:' + reason);
	});

输出如图:

Promise resolve reject

上例中,在创建一个Promise时,给resolve函数传递的是一个拒绝Promise,此时我们发现promise状态是rejected,所以这里第一个参数函数执行,完成的是一个更接近决议的过程(可以参考前文讲述的决议过程),所以命名为resolve是更合理的;而第二个参数函数,则只是拒绝该promise:


	var promise = new Promise((resolve, reject) => {
		reject(Promise.resolve('success'));
	});
	promise.then((msg) => {
		console.log('完成:' + msg);
	}, (reason) => {
		console.log('拒绝:' + reason);
	});

reject函数并不会处理参数,而只是直接将其当做拒绝原因拒绝promise。

Promise实现

Promise是什么,怎么样使用就介绍到此,另外一个问题是面试过程中经常也会被提及的:如何实现一个Promise,当然,限于篇幅,我们这里只讲思路,不会长篇大论。

构造函数

首先创建一个构造函数,供实例化创建promise,该构造函数接受一个函数参数,实例化时,会立即调用该函数,然后返回一个Promise对象:


    var MyPromise = (() => {
    	var value = undefined; // 当前Promise
    	var tasks = []; // 完成回调队列
    	var rejectTasks = []; // 拒绝回调队列
    	var state = 'pending'; // Promise初始为等待态

    	// 辅助函数,使异步回调下一轮事件循环执行
    	var nextTick = (callback) => {
        	setTimeout(callback, 0);
    	};

    	// 辅助函数,传递Promsie的状态值
    	var ref = (value) => {
        	if (value && typeof value.then === 'function') {
            	// 若状态值为thenable对象或Promise,直接返回
            	return value;
        	}
        	// 否则,将最终值传递给下一个then方法注册的回调函数
        	return {
            	then: function(callback) {
                	return ref(callback(value));
            	}
        	}
    	};
    	var resolve = (val) => {};
    	var reject = (reason) => {};

	    function MyPromise(func) {
            func(resolve.bind(this), reject.bind(this));
        }
 
        return MyPromise;
    });

静态方法

在实例化创建Promise时,我们会将构造函数的两个静态方法:resolvereject传入初始函数,接下来需要实现这两个函数:


	var resolve = (val) => {
		if (tasks) {
            value = ref(val);
            state = 'resolved'; // 将状态标记为已完成
            // 依次执行任务回调
            tasks.forEach((task) => {
                value = nextTick((val) => {task[0](self.value);});
            });
            tasks = undefined; // 决议后状态不可变

            return this;
        }
	};
	var reject = (reason) => {
		if (tasks) {
            value = ref(reason);
            state = 'rejected'; // 将状态标记为已完成

            // 依次执行任务回调
            tasks.forEach((task) => {
                nextTick((reason) => {task[1](value);});
            });
            tasks = undefined; // 决议后状态不可变

            return this;
        }
	};

还有另外两个静态方法,原理还是一样,就不细说了。

实例方法

目前构造函数,和静态方法完成和拒绝Promise都已经实现,接下来需要考虑的是Promise的实例方法和链式调用:


	MyPromise.prototype.then = (onFulfilled, onRejected) => {
		onFulfilled = onFulfilled || function(value) {
            // 默认的完成回调
            return value;
        };
        onRejected = onRejected || function(reason) {
            // 默认的拒绝回调
            return reject(reason);
        };

        if (tasks) {
			// 未决议时加入队列
             tasks.push(onFulfilled);
             rejectTasks.push(onRejected);
        } else {
			// 已决议,直接加入事件循环执行
             nextTick(() => {
                 if (state === 'resolved') {
                     value.then(onFulfilled);
                 } else if (state === 'rejected') {
                     value.then(onRejected);
                 }
             });
        }

        return this;
	};

实例

以上可以简单实现Promise部分异步管理功能:


	var promise = new MyPromise((resolve, reject) => {
		setTimeout(() => {
			resolve('完成');
		}, 0);
	});
	promise.then((msg) => {console.log(msg);});

本篇由回调函数起,介绍了回调处理异步任务的常见问题,然后介绍Promises/A+规范及Promise使用,最后就Promise实现做了简单阐述(之后有机会会详细实现一个Promise),花费一周终于把基本知识点介绍完,下一篇将介绍JavaScript异步与生成器实现。

参考

  1. Promises/A+ specification
  2. JavaScript Promise

欢迎访问我的个人博客

© 著作权归作者所有

共有 人打赏支持
熊建刚
粉丝 64
博文 21
码字总数 88314
作品 0
海淀
程序员
加载中

评论(2)

熊建刚
熊建刚

引用来自“jerryoo”的评论

是否可以把回调地狱的用例修改一下,不要用setTimeout,而是用ajax的嵌套。
其实本质是一样的,都是传递回调函数,异步任务结束后执行回调,用setTimeout代码稍微简单一点,也方便测试。:smile:
jerryoo
jerryoo
是否可以把回调地狱的用例修改一下,不要用setTimeout,而是用ajax的嵌套。
探索Javascript异步编程

笔者在之前的一片博客中简单的讨论了Python和Javascript的异同,其实作为一种编程语言Javascript的异步编程是一个非常值得讨论的有趣话题。 JavaScript 异步编程简介 回调函数和异步执行 所谓...

naughty
2014/05/22
0
8
异步JavaScript与Promise

异步? 我在很多地方都看到过异步(Asynchronous)这个词,但在我还不是很理解这个概念的时候,却发现自己常常会被当做“已经很清楚”(* ̄ロ ̄)。 如果你也有类似的情况,没关系,搜索一下这个...

银月光海
2015/02/02
0
0
JS的Event Loop 和 microTask

面试和笔试题目中,经常会出现'promise','setTimeout'等函数混合出现时候的运行顺序问题。 我们都知道这些异步的方法会在当前任务执行结束之后调用,但为什么'promise'会在'setTimeout'之前执...

Vincent Ko
08/06
0
0
还在找什么,JavaScript的异步编程解决方案全在这里了

阿里巴巴前端工程师逸翾对JavaScript中的异步编程进行了详细讲解。JavaScript的特点就是单线程,本文首先对单线程异步的原理进行了解读,接着着重分析了JavaScript异步解决方案,详述了Callb...

云迹九州
04/28
0
0
说一说javascript的异步编程

众所周知javascript是单线程的,它的设计之初是为浏览器设计的GUI编程语言,GUI编程的特性之一是保证UI线程一定不能阻塞,否则体验不佳,甚至界面卡死。 所谓的单线程就是一次只能完成一个任...

轩轩1483710814598
08/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

多线程

1. 多线程概念。并发和并行的概念。 多线程指的是一段时间内cpu同时执行多个线程。一个程序至少运行>=1个进程,进程就是运行中的程序,而一个进程至少运行>=1个线程,线程是操作系统能调度的...

鱼想吃肉
今天
0
0
HBase 表修复在线方式和离线方式

一、在线修复 1.1 使用检查命令 $ ./bin/hbase hbck 该命令可完整修复 HBase 元数据信息;存在有错误信息会进行输出; 也可以通过如下命令查看详细信息: $ ./bin/hbase hbck -details 1.2 ...

Ryan-瑞恩
今天
3
0
redis 系列二 -- 常用命令

1.基础命令 info ping quit save dbsize select flushdb flushall 2.键命令 2.1 set 直接赋值 set a a 2.2 get 取值 get a 2.3 exists 是否存在 exists a 2.4 expire 设置剩余时间 秒 expire......

imbiao
今天
2
0
php foreach

<?php// 数组的引用$a=array(1,2,3,4,5);foreach($a as $key=>&$value){$value=$value*2;}print_r($a);echo " $key -------------------$value\r\n";/** * ...

小张525
今天
3
0
12-利用思维导图梳理JavaSE-多线程

12-利用思维导图梳理JavaSE-多线程 主要内容 1.线程概念 2.线程开发 3.线程的状态 4.线程的同步和死锁 5.Java5.0并发库类 QQ/知识星球/个人WeChat/公众号二维码 本文为原创文章,如果对你有一...

飞鱼说编程
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部