promise
what is promise
MDN 里面有详细的简介
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises
1.同步和异步
我们知道js的执行环境是单线程的。所谓的单线程,是指js引擎中负责解释和执行js代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完才能开始下一个,它会阻止其他的任务,就是js代码自上而下的执行,只能等待上面的代码全部执行完事才能开始它的下面的代码的执行。这个任务可称为主线程。但是实际上还有其他的线程,如事件触发线程、ajax请求线程等。这也就引发了同步和异步的问题哦。
1.1 同步
同步模式,就是上面所说的单线程模式, 一次只能执行 一个任务,函数调用后需要等到函数执行结束,返回执行的结果,才能执行下一个任务。如果这个任务执行的时间较长,就会导致 线程阻塞
例1.1
const x = true
while(x);
console.log("don not carry me !!!")
上面的例子就是同步模式,其中的while是一个死循环,它会阻塞进程,因此console.log是在控制台打印不出来的。
同步模式比较简单,也比较容易编写。但问题也显而易见,如果请求的时间比较长,而阻塞了后面代码的执行,体验是很不好的。因此对于一些耗时的操作,异步模式则是更好的选择。
1.2 异步
异步模式,即与同步模式相反,可以一起执行 多个任务,函数调用后不会立即返回执行的结果,如果任务A需要等待,可先执行任务B,等到任务A结果返回后再继续回调。
最常见的模式就是定时器了,我们来看看下面的例子
setTimeout(function(){
console.log('taskA,asynchronous')
},0)
console.log('taskB,synchronous')
// while(x);
控制台的输出为
taskB,synchronous
taskA,asynchronous
我们可以看到,定时器延时的时间明明为0,但taskA还是晚于taskB执行。这是为什么呢???由于定时器是异步的, 异步任务会在当前脚本的所有同步任务执行完才会执行。 如果同步代码中含有死循环,即将上面的while的注释去掉,那么这个异步任务就不会执行,因为同步任务阻塞了进程。
1.3回调函数
说到异步,就一定要说一下回调函数了。上面的例子中,setTimeout里面的function便是回调函数。可以简单理解为:(执行完)回(来)调(用)的函数。
回调函数是一段可执行的代码段,它以参数的形式传递给其他代码,在合适的时间执行这段(回调函数)的代码。
回调函数不仅可以用于异步调用,一般同步的场景也可以用回调。在同步调用下,回调函数一般是最后执行的。而在异步调用下,可能一段时间后执行或者步执行。
//同步回调
var fun1 = function(callback){
//do something
//这个地方一定要加分号否则会报错
console.log('before callback');
(callback && typeof(callback) === 'function') && callback()
console.log('after callback')
}
var fun2 = function(param){
// do something
var start = new Date()
while((new Date() - start) < 3000){
}
console.log('i am callback')
}
fun1(fun2)
输出结果为:
before callback
i am callback
after callback
由于是同步回调,会阻塞后面的代码,如果fun2是个死循环,后面的代码就不会再执行了。setTimeout是常见的异步回调,另外的一个异步回调即ajax请求。
2.为什么要使用Promise
说完以上的基本概念,我们就可以继续学习Promise了
上面提到,Promise对象是用于异步操作的,既然我们可以使用异步回调函数来进行异步操作,为什么还要引入一个promise新概念,还要花时间学习它呢,我们可以看看Promise的过人之处。利用Promise改写异步回调。
function sendRequest(url,param){
return new Promise(function(resolve,reject){
request(url,param,resolve,reject)
})
}
sendRequest('test.html','').then(function(data){
//异步操作成功后的回调
console.log('请求成功了,这是返回的数据:',data)
},function(error) {
//异步操作失败后的回调
console.log('请求失败了,这是返回的数据:',error)
})
这么一看,并没有什么大的区别,还比上面的异步回调复杂,需要先新建Promise再定义其回调。其实,Promise的真正强大之处在于它的多重链式调用,可以避免层层嵌套回调。如果我们在第一次ajax请求后,还要用它返回的结果再次请求呢???
request('test.html','',function(data1){
console.log('这是第一次请求成功,这是返回的数据:',data1)
request('test.html','data1',function(data2){
console.log('这是第二次请求成功,这是返回的数据:',data2)
request('test.html','data2',function(data3){
console.log('这是第三次请求成功,这是返回的数据:',data3)
},function(error3){
console.log('这是第三次请求失败,这是返回的数据:',error3)
})
},function(error2){
console.log('这是第二次请求失败,这是返回的数据:',error2)
})
},function(error1){
console.log('这是第一次请求失败,这是返回的数据:',error1)
})
上面的代码出现了多层回调嵌套,有种晕头转向的感觉,编程体验十分不好,而使用Promise,我们就可以使用then进行链式回调,将异步操作以同步方式操作的流程表示出来。
sendRequest('test.html','').then(function(data1){
console.log('这是第一次请求成功,这是返回的数据:',data1)
return sendRequest('test2.html',data1)
}).then(function(data2){
console.log('这是第二次请求成功,这是返回的数据:',data2)
return sendRequest('test3.html',data2)
}).then(function(data3){
console.log('这是第三次请求成功,这是返回的数据:',data3)
}).catch(function(error){
//使用catch捕获前面的错误
console.log('这是第一次请求失败,这是失败的信息:',error)
})
是不是感觉到清晰明显一些,下面我们开始正式进入Promise的学习。
3 Promise的基本用法
3.1 基本用法
上面我们认识了promise长什么样,但对它用到的resolve,rejected,then,catch想必还不了解。
Promise对象代表一个未完成、但是预计将来会完成的操作。
它有以下三种状态:
- pending:初始值,不是fulfilled,也不是rejected
- fulfilled:代表操作成功
- rejected:代表操作失败
promise有两种状态改变的方式,既可以从pending转变为 fulfilled,也可以从pending转变为rejected。一旦状态改变,就不变了。当状态发生变化,promise.then绑定的函数就会被调用。
promise一旦新建就会立即执行,无法取消。这是它的缺点之一。
var promise = new Promise(function(resolve,reject){
if(/*异步操作成功*/){
resolve(data)
}else{
reject(error)
}
})
类似构建对象,我们使用new来构建一个 promise。 promise接受一个函数作为参数,该函数的两个参数分别是resolve和reject。这两个函数就是回调函数,由js引擎提供。
resolve函数的作用:在异步操作成功时的调用,并将异步的操作结果,作为参数传递出去;
reject函数的作用:在异步操作失败时的调用,并将异步操作报出的错误,作为参数传递出去;
promise实例生成以后,可以用then方法指定resolved状态和reject状态的回调函数。
promise.then(onFulfilled,onRejected);
promise.then(function(data){
// do something when success
},function(error){
// do something when fail
})
then方法会返回一个Promise。then就是定义resolve和reject函数的,其resolve参数相当于:
function resolveFun(data){
// data为promise传出的值
}
而新建Promise中的’resolve(data)’,则相当于执行resolveFun函数。
Promise新建后就会立即执行。而then方法中指定的回调函数,将在当前脚本所有同步任务执行完才会执行。
var promise = new Promise(function(resolve,reject){
console.log('before resolved')
resolve()
console.log('after resolved')
})
promise.then(function(){
console.log('resolved')
})
console.log('outer')
输出顺序:
before resolved
after resolved
outer
resolved
3.2基本API
.then()
语法:promise.prototype.then(onFufilled,onRejected)
对promise添加onFufilled,onRejected回调,并返回的是一个新的promise实例,且返回值将作为参数传入这个新的promise的resolve函数。
.catch()
语法:promise.prototype.then(onRejected)
该方法是.then(undefined,onRejected)的别名,用于指定发生错误时的回调函数。
promise.then(function(data){
console.log(success)
}).catch(function(error){
console.log(error)
})
//等同于
promise.then(function(data){
console.log(success)
}).catch(undefined,function(error){
console.log(error)
})
var promise = new Promise(function(resolve,reject){
throw new Error('test')
})
//等同于
var promise = new Promise(function(resolve,reject){
reject(new Error('test'))
})
//用catch捕获
promise.catch(function(error){
console.log(error)
})
输出:test
reject方法的作用就等同于抛错。
promise对象的错误,会一直向后传递,直到被捕获。即错误总会被下一个catch所捕获。then方法指定的回调函数,若抛出错误,也会被下一个catch捕获。catch中也能抛错,则需要后面的catch来捕获。
.all()
语法:Promise.all(iterable)
该方法用于将多个Promise实例,包装成一个新的Promise实例
var p = Promise.all([p1,p2,p3])
Promise.all方法接受一个数组做参数,数组中的对象(p1,p2,p3)均为promise实例,它的状态将由这3个promise实例来决定。
- 当p1,p2,p3状态都变成Fulfilled,p的状态才会变成Fulfilled,并将三个promise返回的结果,按参数的顺序(而不是resolve()的顺序)存入数组,传给p的回调函数
var p1 = new Promise(function(resolve,reject){
setTimeout(resolve,3000,'first')
})
var p2 = new Promise(function(resolve,reject){
resolve('second')
})
var p3 = new Promise(function(resolve,reject){
resolve(resolve,1000,'third')
})
Promise.all([p1,p2,p3]).then(function(values){
console.log(values)
})
约3秒后
[‘first’,‘second’,‘third’]
- 当p1,p2,p3其中之一状态变为rejected,p的状态也会变成rejected,并把第一个被reject的promise的返回值,传给p的回调函数。
var p1 = new Promise(function(resolve,reject){
setTimeout(resolve,1000,'first')
})
var p2 = new Promise(function(resolve,reject){
resolve(resolve,2000,'second')
})
var p3 = new Promise(function(resolve,reject){
reject('third')
})
Promise.all([p1,p2,p3]).then(function(values){
console.log(values)
}).catch(function(error){
console.log(error)
})
输出:
three
多个promise是同时开始执行的,并行执行的,而不是顺序执行的。
function timePromisefy(delay){
return new Promise(function(resolve){
setTimeout(function(){
resolve(delay);
},delay)
})
}
var startDate = Date.now()
Promise.all([
timePromisefy(1),
timePromisefy(32),
timePromisefy(64),
timePromisefy(128),
]).then(function(values){
console.log(Date.now() - startDate +'ms')
})
最后输出为133ms //会大于128,但不是所有的时间总和
.resolve()
Promise.resolve(value)
Promise.resolve(promise)
Promise.resolve(thenable)
它可以看作是new Promise()的快捷方式
Promise.resolve('success')
//等同于
new Promise(function(resolve){
resolve('success')
})
.reject()
Promise.reject(reason)
它可以看作是new Promise()的快捷方式
Promise.resolve('success')
//等同于
new Promise(function(resolve,reject){
reject(new Error('error'))
})
4.总结
创建promise的流程:
1.使用new Promise(fn)或者它的快捷方式Promise.resolve()、Promise.reject(),返回一个promise对象
2.在fn中指定异步的处理
处理结果正常,调用resolve
处理结果错误,调用reject
最后希望大家能够有所收获!!!