简单来说同步和异步可以这么形容。
同步:等待结果
异步:不等待结果
需要注意的是异步常常伴随回调一起出现,但是异步不是回调,回调也不一定是异步。
同步的 sleep:
function sleep(seconds){
var start = new Date()
while(new Date() - start < seconds * 1000){
}
return
}
console.log(1)
sleep(3)
console.log('wake up')
console.log(2)
// 打印顺序是 1, wake up, 2
js在这一段时间内,会一直去查看时间到了没有,时间到了再去执行相应的代码,也就是说js会一直等下去,等待的过程不做任何事情,就是去反复的查看时间到了没有。
异步的 sleep:
function sleep(seconds, fn){
setTimeout(fn, seconds * 1000)
}
console.log(1)
sleep(3, ()=> console.log('wake up'))
console.log(2)
// 打印顺序是 1, 2, wake up
根据上面的代码可以看出,js在执行代码的时候是不会等待异步代码的结果,而是直接去执行了下面的代码。那么js看到异步代码会做什么昵?其实很简单,就是告诉浏览器,我这有个异步代码,你帮我订个闹钟,时间到了通知我,我去调用一下。这也是为什么js是单线程,可效率很高的原因,因为有很多人帮它做事。
异步的例子:
获取图片的宽度,但是图片加载到页面时需要时间的,因此下面的代码获取到的图片宽度为0:
document.getElementsByTagName('img')[0].width // 宽度为 0
console.log('done')
解决方法:img加载成功后就会触发onload,这样的话就可以在onload后可以获取到width了
document.getElementsByTagName('img')[0].onload = function(){
console.log(this.width) // 宽度不为 0
console.log('real done')
}
console.log('done')
AJAX 中的异步
同步:
let request = $.ajax({
url: '.',
async: false // 会一直等待响应
})
console.log(request.responseText)
异步:
$.ajax({
url: '/',
async: true,
success: function(responseText){ // 执行完会来调用这个函数
console.log(responseText)
}
})
console.log('请求发送完毕') // 请求完,不等响应,就执行本行代码
两种方式可以获取异步的结果
- 轮询: 隔一段时间就去查看一下,看有没有结果。
- 回调
回调的形式
1.Node.js 的 error-first 形式
fs.readFile('./1.txt', (error, content)=>{
if(error){
// 失败
}else{
// 成功
}
})
- jQuery 的 success / error 形式
$.ajax({
url:'/xxx',
success:()=>{},
error: ()=>{}
})
- jQuery 的 done / fail / always 形式
$.ajax({
url:'/xxx',
}).done( ()=>{} ).fail( ()=>{} ).always( ()=> {})
- Prosmise 的 then 形式
$.ajax({
url:'/xxx',
}).then( ()=>{}, ()=>{} ).then( ()=>{})
promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。使用回调来获取异步结果,而获取可能成功或者失败,因此不同的人就产生了如上不同的写法,多种写法不够统一,因此就出现了promise规范,promise就是来解决获取结果成功或者失败的命名规范和形式,then(成功函数,失败函数),成功函数和失败函数里面是获取到结果后所做的动作,如 then(成功函数,失败函数)。promise是对函数回调形式的一种规范。
在promise/A+规范中,如链式then里面的第一个then不管是成功函数和失败函数的顺利执行,都会去执行第二个then里面的成功函数,以此类推。但是第一个then里面不管是成功函数和失败函数出现了异常,那么会有第二个then里面的失败函数来接住这个异常。而catch也是捕获异常的方法,通常用来兜底的。相当于then的一个语法糖,也就是第一个(成功)参数传undefined,then(undefined,失败函数)。
自己返回 Promise
function ajax(){
return new Promise((resolve, reject)=>{
做事
如果成功就调用 resolve
如果失败就调用 reject
})
}
var promise = ajax()
promise.then(successFn, errorFn)
async / await
把异步代码变成同步代码,也就是用同步的形式写异步代码。await后面跟的是一个会返回promise的函数。一个函数里面有await,那么外面最好写一个async,不然会报错。
实现一个简单的Promise
满足以下需求:
function Promise(???){
???
return ???
}
var promise = new Promise(function(x,y){
setTimeout(()=>{
x(101)
}, 3000)
})
promise.then((z)=>{
console.log(z) // 101
})
分析:
首先搞清楚输入和输出是什么!!!涉及到传参为函数的话,要考虑到函数的调用,没有this的话,就把call的第一个参数设为undefined。而Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。通常我们把pending变为fulfilled的过程称为resolved,把pending变为rejected的过程称为rejected。
代码
function Promise(fn){
var status = 'pending' // 记录初始状态
function successNotify(){
status = 'resolved'
toDoThen.apply(undefined, arguments)
}
function failNotify(){
status = 'rejected'
toDoThen.apply(undefined, arguments)
}
function toDoThen(){
setTimeout(()=>{ // 保证回调是异步执行的
if(status === 'resolved'){
for(let i =0; i< successArray.length;i ++) {
successArray[i].apply(undefined, arguments)
}
}else if(status === 'rejected'){
for(let i =0; i< failArray.length;i ++) {
failArray[i].apply(undefined, arguments)
}
}
})
}
var successArray = [] // 创建成功队列
var failArray = [] // 创建失败队列
fn.call(undefined, successNotify, failNotify)
return {
then: function(successFn, failFn){
successArray.push(successFn) // 把成功函数放进成功队列里面
failArray.push(failFn) // 把失败函数放进失败队列里面
return undefined // 简化, 链式then的话可继续返回then
}
}
}