欢迎各位兄弟 发布技术文章
这里的技术是共享的
在JavaScript的世界中,所有代码都是单线程执行的。
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现:
function callback() {
console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');
观察上述代码执行,在Chrome的控制台输出可以看到:
before setTimeout()
after setTimeout()
(等待1秒后)
Done
可见,异步操作会在将来的某个时间点触发一个函数调用。
AJAX就是典型的异步操作。以上一节的代码为例:
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
return success(request.responseText);
} else {
return fail(request.status);
}
}
}
把回调函数success(request.responseText)
和fail(request.status)
写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。
有没有更好的写法?比如写成这样:
var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
.ifFail(fail);
这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success
函数或fail
函数。
古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。
Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持。先测试一下你的浏览器是否支持Promise:
我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:
function test(resolve, reject) {
var timeOut = Math.random() * 2;
log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function () {
if (timeOut < 1) {
log('call resolve()...');
resolve('200 OK');
}
else {
log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
}
这个test()
函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve('200 OK')
,如果执行失败,我们将调用reject('timeout in ' + timeOut + ' seconds.')
。可以看出,test()
函数只关心自身的逻辑,并不关心具体的resolve
和reject
将如何处理结果。
有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:
var p1 = new Promise(test);
var p2 = p1.then(function (result) {
console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
console.log('失败:' + reason);
});
变量p1
是一个Promise对象,它负责执行test
函数。由于test
函数在内部是异步执行的,当test
函数执行成功时,我们告诉Promise对象:
// 如果成功,执行这个函数:
p1.then(function (result) {
console.log('成功:' + result);
});
当test
函数执行失败时,我们告诉Promise对象:
p2.catch(function (reason) {
console.log('失败:' + reason);
});
Promise对象可以串联起来,所以上述代码可以简化为:
new Promise(test).then(function (result) {
console.log('成功:' + result);
}).catch(function (reason) {
console.log('失败:' + reason);
});
实际测试一下,看看Promise是如何异步执行的:
Log:
start new Promise...
set timeout to: 0.04930280052661917 seconds.
call resolve()...
Done: 200 OK
可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:
Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。
要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:
job1.then(job2).then(job3).catch(handleError);
其中,job1
、job2
和job3
都是Promise对象。
下面的例子演示了如何串行执行一系列需要异步计算获得结果的任务:
Log:
start new Promise...
calculating 123 x 123...
calculating 15129 + 15129...
calculating 30258 x 30258...
calculating 915546564 + 915546564...
Got value: 1831093128
setTimeout
可以看成一个模拟网络等异步执行的函数。现在,我们把上一节的AJAX异步执行函数转换为Promise对象,看看用Promise如何简化异步处理:
除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()
实现如下:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()
实现:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
由于p1
执行较快,Promise的then()
将获得结果'P1'
。p2
仍在继续执行,但执行结果将被丢弃。
如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。
MT 请求查询ip=223.5.5.5归属地
NT 尝试获取,得到两种可能结果:完成 或 失败(如拔网线,缺省部分链接资源地址)
MT将fetch的结果包装到Promise对象p1中,选择执行其后then中的两个函数,并再次将结果包装到p2中
MT调用p2的then方法,显示状态内容
虽然知道原因,但我觉得我讲起来你们可能听不懂,因为要阅读文档,了解了这些方法的参数和返回值才会懂。我大概说一下吧: 输出大于1 的时候,执行 catch 的回调函数时输出了 Done: timeout in 1.4453681319996927 seconds. 然后因为 catch 的回调函数没有返回值,所以 catch 方法返回的 promise 对象的状态变为了 'resolved',value 值变为了 undefined。由于 catch 方法返回的 promise 对象状态变为 'resolved',所以触发了then 方法的回调函数,输出了 'Failed: ' + value, 即:Failed: undefined ...... 是不是看得很糊涂,不明白的同学还是多看看官方文档吧
回复
catch方法返回的promise对象并不是这个对象
这个说法也不对吧,
p1 = promise.catch(response => {
console.log(promise);
console.log(response);
})
没有返回 promise啊,这里只是一个箭头函数,并没有返回任何东西.........
回复
这和箭头函数有什么关系呢?
我指的是promise的catch方法会返回一个新的promise对象
按照mdn的promise页面
我们可以用
promise.then()
,promise.catch()
和promise.finally()
这些方法将进一步的操作与一个变为已敲定状态的 promise 关联起来。这些方法还会返回一个新生成的 promise 对象,这个对象可以被非强制性的用来做链式调用
而在您最初的发言中在.then与.catch的语句中的箭头函数中始终调用的都是最初的promise对象,输出结果必然会是一样的且没有意义。
建议您在发言之前请多多思考一下。
resolve和reject代表什么意思?
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
也就是它们有两个作用:1、改变promise状态; 2、将操作结果作为参数传出去,例如 resolve( result ), 这样后面调用then 才能直接得到 这个result,并将得到的result 作为参数用于 then 里面的函数操作
情景化记忆:在一个任务链中,比如我要向上级部门一层层的往上提交申请,if(某种条件)承诺帮你resolve解决问题,else承诺reject你的请求. 他给出的resolve问题的办法只是个空头Promise,then到总经理那实现具体承诺,如果总经理还是给一个空头承诺(返回Promise实例),还得then到董事长那里.... 任一一步做出的是reject的承诺,还有什么好说的,被拒绝了,后面的就不会再往上走了呀. 准备catch 拒绝通知吧blablabla
都要成功才进入then,返回结果数组.
then()和catch(),谁先调用算谁的,其它任务中断.
then( function )串行执行任务,代码可读性强 catch( function )
all( array )模拟并行执行任务
rece( array )执行响应时间短的,其他丢弃
大佬,讲得很好,三个例子都很棒,感谢!但第一遍看着有点没明白,您可以考虑把setTimeout函数的介绍以及自己定义的log函数顺序放前面一点,那就更完美了。:-)
感觉 js 和 C++ 差别还是很大呀。。
不明白log是什么意思呢,执行之后显示未定义
文件处理、ajax、promise都讲的不是很清晰。可能代码演示比较好表达一点吧。廖神码字还是扯不清楚。
看得一头雾水,扩展阅读 http://es6.ruanyifeng.com/#docs/promise ,能了解一些基本信息,再来看可能更好理解一些~
经找资料后自己的理解:JS引擎是单线程的,但是浏览器是多线程的,其执行的方法如setTimeOut
是由浏览器来调度的,计时器也是一个单独的线程(并不是JS引擎来计时)。而Promise.all(iterable) 方法返回一个 Promise 实例,其内部方法串行执行,比如P1先计时完成,浏览器的触发线程就将P1方法放到JS队列中由JS引擎来执行。
既然JS是单线程,那么Promise.all是如何实现并行执行的?
function promise (URL) { let resdfile = new Promise(function (resolve,reject) { fs.readFile(URL,function (err,data) { if (err){ reject(err) } if (data){ resolve(data) } }) }) return resdfile }
promise("testa.txt").then(function (data) { console.log("done:"+data) return promise("test.txt") }).then(function (data) { console.log("done:"+data) }).catch(function (err) { console.log("fail:"+err) })
可不可能promise对象在new出来之后到调用then()方法之前的这段时间内,异步方法就已经执行结束了?如果可能的话,这段时间里调用resolve方法岂不是会报错(因为还没来得及调用then()方法而未得到赋值)?
Promise结果会缓下来,比如看下面这段代码
var promise = Promise.resolve(5); // already resolved here
setTimeout(function () { promise.then(function (x) { console.log(x); // still logs 5 }); }, 1000);
setTimeout(resolve, 500, 'P1') 之前学setTimeout的时候只有两个参数,一个函数一个延迟时间;这里三个参数代表什么,为什么要这么写? resolve在这里代表的是成功的状态吗?‘P1’返回值为什么要写到时间的后面?
job1.then(job2).then(job3).catch(handleError);
其中,job1、job2和job3都是Promise对象。 不明白 我觉得job1是Promise对象,job2,3应该是异步函数
timeout*1000是啥
来自 https://www.liaoxuefeng.com/wiki/1022910821149312/1023024413276544
Promise里resolve(arg)里的参数arg是Promise对象的情况
按我的理解,resolve会把其参数交给then中的函数,作为回调的参数,但我发现这样一份代码,其中将第一个Promise对象传给了第二个Promise中的resolve,而回调函数是打印。但事实上,打印的不是Promise对象,最终打印的是第一个对象中resolve的参数。但then方法的调用者确确实实是第二个对象。注意这不是链式调用中return一个Promise的写法,而是Promise是参数。谁能解释一下这种情况下then为什么能获得非调用对象传过来的参数。
Read More
如果
then
中的回调函数返回一个未定状态(pending
)的 Promise,那么then
返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。MDN上这句应该可以解释你这个问题吧
then中的回调函数就是个打印语句,没有返回值
全部讨论 回复
then串联里setTimeout不同方式导致的不同结果???
function log(txt)
{
console.log(txt);
}
function multiply(input) {
return new Promise(function (resolve, reject) {
log('calculating ' + input + ' x ' + input + '...');
// setTimeout(resolve, 500, input*input);
setTimeout(resolve(input*input), 500);
});
}
function add(input) {
return new Promise(function (resolve, reject) {
log('calculating ' + input + ' + ' + input + '...');
// setTimeout(resolve, 500, input+input);
setTimeout(resolve(input+input), 500);
});
}
var p = new Promise(function (resolve, reject) {
log('start new Promise...');
resolve(123);
});
p.then(multiply)
.then(add)
.then(multiply)
.then(add)
.then(function (result) {
log('Got value: ' + result);
});
结果直接就全部打印出来了,没有延时。
查了setTimeout的用法,第三个参数就是传给第一个参数(函数)的参数,应该和resolve(input*input)一样啊,那为什么写成resolve(input*input)就没有延时效果了呢?
Read More
setTimeout 的第一个参数是回调函数的入口(地址),也就是resolve,不是可执行语句resolve(input*input),resolve一旦执行,会直接将内容传递给Promise对象的PromiseResult,写成可执行语句,setTimeout可能会获得一个未定义或未知的函数入口,具体可以调试看看。
全部讨论 回复
Promise 对象
浏览器前端显示进程(render process)里有很多线程,其中执行js代码的是主线程(main thread,MT)
通过异步(async)方法,可以将当前任务交给其他线程(或进程)如网络线程(network thread,NT)并行处理。
如上过程很简单
Promise 是一个语法糖,它可以将一个异步操作结束时的状态(成败)、对应的数据打包,再通过promise.then,将对应的处理函数也捆绑上去。
Promise 方法以一个函数f为参数,当创建一个Promise对象 p 后,p 调用 f(resolve, reject),resolve和reject这两个函数会将其在函数 f 中接受到的内容专递给Promise对象的PromiseResult属性,并更新PromiseState——如果调用的是resolve则为'fulfilled',反之为'rejected' 。如果谁都没有调用,则为'pending'等待中。如果在f中多次重复调用这两函数,则最先调用的函数有效,之后的无效。
p.then 会根据
Read More
p.then 会根据 p 的PromiseResult选择调用回调函数,并将之前调用f获得的内容专递给相应回调函数。
Read More
全部讨论 回复