为什么要有Promise?
Promise是异步编程的一种解决方案。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。Promise对象提供了统一的接口,使得控制异步操作更加容易。
目录结构:
一、Promise的特点
二、基本用法
三、Promise的方法
四、
一、Promise的特点
1、对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
二、基本用法
const promise = new Promise(function(resolve, reject){ // ... some code if(/*异步操作成功*/){ resolve(value); }else{ reject(error); } })
Promise的构造函数参数是一个函数,该函数的两个参数分别是resolve和reject,它们是两个函数,由JavaScript提供,不用自己部署。
resolve函数的作用:将Promise对象的状态由pending变为resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态由pending变为rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去,这个参数是可选的。
promise.then(function(value){ //success }, function(error){ // 这个函数是可选的,不一定要提供 //failure })
使用Promise的栗子:Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。
function loadImageAsync(url) { return new Promise(function(resolve, reject){ const image = new Image(); image.onload = function(){ resolve(image); }; image.onerror = function(){ reject(new Error('Could not load image at '+url) } image.src = url }) }
用Promise对象实现Ajax操作
const getJSON = function (url) { const promise = new Promise(function (resolve, reject) { const handler = function () { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response) } else { reject(new Error(this.statusText)) } } const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onreadystatechange = handler; xhr.responseType = 'json'; xhr.setRequestHeader('Accept', 'application/json'); xhr.send(); }); return promise; } getJSON('/posts.json').then(function (json) { console.log('Contents:' + json) }, function (error) { console.log('出错了', error) })
getJSON内部,resolve函数和reject函数调用时,都带有参数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例。
下图一、promise内部调用reject函数之后,在then方法内第二个参数函数里面输出抛出的错误。
下图二、(同上)虽然在最后有catch语句,但是不会被调用。
下图三、promise内部调用reject函数之后,没有then方法的第二个参数,那么会在catch方法里面输出抛出的错误。
var promise = new Promise(function(resolve, reject){ reject(new Error('123')) }).then(function(data){ console.log(data) },function(error){ console.log(1); console.log(error) }) //输出: 1 Error: 123
var promise = new Promise(function(resolve, reject){ reject(new Error('123')) }).then(function(data){ console.log(data) },function(error){ console.log(1); console.log(error) }).catch(function(err){ console.log(2); console.log(err) }) //输出: 1 Error: 123
var promise = new Promise(function(resolve, reject){ reject(new Error('123')) }).then(function(data){ console.log(data) }).catch(function(err){ console.log(2); console.log(err) }) //输出: 2 Error: 123
如果在then方法里面的第二次参数里面return new promise,那么会在catch里面捕获错误
var promise = new Promise(function(resolve, reject){ reject(new Error('123')) }).then(function(data){ console.log(data) },function(error){ console.log(1); return new Promise(function(resolve, reject){reject(new Error('abc'))}) }).catch(function(err){ console.log(2); console.log(err) }) //输出: 1 2 Error: abc
catch方法并没有捕获到then方法内的new Error错误
var promise = new Promise(function(resolve, reject){ resolve('123') }).then(function(data){ console.log(data) console.log(new Error('error')); },function(error){ console.log(1); }).catch(function(err){ console.log(2); console.log(err) }) //输出 123 Error: error
并没有在catch方法里面输出error。
var promise = new Promise(function(resolve, reject){ resolve('123') }).then(function(data){ console.log(data) return new Promise((resolve, reject)=>{new Error('error')}); },function(error){ console.log(1); }).catch(function(err){ console.log(2); console.log(err) }) //输出 123
只有promise里面调用resolve、reject函数才会在then方法里面有输出
var promise = new Promise(function(resolve, reject){ resolve('123') }).then(function(data){ console.log(data) return new Promise((resolve, reject)=>{resolve(new Error('error'))}); },function(error){ console.log(1); }).then(function(data){console.log(data)}) .catch(function(err){ console.log(2); console.log(err) }) //输出: 123 Error: error
var promise = new Promise(function(resolve, reject){ resolve('123') }).then(function(data){ console.log(data) return new Promise((resolve, reject)=>{reject(new Error('error'))}); },function(error){ console.log(1); }).then(function(data){console.log(data)}) .catch(function(err){ console.log(2); console.log(err) }) //输出: 123 2 Error: error
const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }) const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2.then(result => console.log(result)) .catch(error => console.log(error)) //输出 Error: fail
var p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 0) }) var p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2.then(result => console.log(result)) .catch(error => console.log(error)) //输出 Promise {<pending>} Error: fail
resolved的Promise是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
new Promise((resolve, reject) => { resolve(1); console.log(2); }).then(r => { console.log(r); }); //输出 2 1
new Promise(function (resolve, reject) { resolve('123') }).then(function (data) { console.log(data) }).then(function (data) { console.log(data) }) //输出 123 undefined
new Promise(function (resolve, reject) { resolve('123') }).then(function (data) { console.log(data) return new Promise(function (resolve, reject) { reject('234') }) }).then(function (data) { console.log(data) }).catch(function (data) { console.log(data) }) //输出 123 234
三、Promise的方法
1、Promise.prototype.then()
then方法返回的是一个新的Promise实例,注意,不是原来那个Promise实例。因此可以采用链式写法,即then方法后面在调用另一个then方法。
function getPromise(data) { return new Promise(function (resolve, reject) { resolve(data) }) } getPromise({ aaa: 'bbb' }).then(function (param) { return param }).then(function (param2) { console.log(param2.aaa) }) //输出 bbb
上面的then方法,依次指定了两个回调函数,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
2、Promise.prototype.catch()
Promise.prototype.catch方法是 .then(null, rejection)或 .then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function(posts){ // ... }).catch(function(error){ // 处理getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error) })
getJSON
方法返回一个 Promise 对象,如果该对象状态变为resolved
,则会调用then
方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected
,就会调用catch
方法指定的回调函数,处理这个错误。另外,then
方法指定的回调函数,如果运行中抛出错误,也会被catch
方法捕获。
p.then((val) => console.log('fulfilled:', val)) .catch((err) => console.log('rejected', err)); // 等同于 p.then((val) => console.log('fulfilled:', val)) .then(null, (err) => console.log("rejected:", err));
const promise = new Promise(function (resolve, reject) { throw new Error('test'); }); promise.catch(function (error) { console.log(error); }); //输出 Error: test
上图等价于下图中的方法
// 写法一 const promise = new Promise(function(resolve, reject) { try { throw new Error('test'); } catch(e) { reject(e); } }); promise.catch(function(error) { console.log(error); }); // 写法二 const promise = new Promise(function(resolve, reject) { reject(new Error('test')); }); promise.catch(function(error) { console.log(error); });
reject方法的作用,等同于抛出错误。
Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
function getPromise(data) { return new Promise(function (resolve, reject) { resolve(data) }) } getPromise('123').then(function (param) { return new Promise(function (resolve, reject) { reject(new Error('test1')) }) }).then(function (param2) { return new Promise(function (rsolve, reject) { reject(new Error('test2')) }) }).catch(function (data) {//处理前面三个Promise产生的错误 console.log(data) }) //输出 Error: test1
上面代码中,一共有三个Promise对象:一个由getPromise产生,两个由then产生,它们之中任何一个抛出的错误,都会被最后一个catch捕获。
一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。建议总是使用catch
方法,而不使用then
方法的第二个参数。
// bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。
const someAsyncThing = function () { return new Promise(function (resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2) }) } someAsyncThing().then(function () { console.log('everything is great'); }) setTimeout(() => { console.log(123) }, 2000) //输出 Uncaught (in promise) ReferenceError: x is not defined 123
someAsyncThing
函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined
,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123
。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。
const promise = new Promise(function (resolve, reject) { resolve('ok'); setTimeout(function () { console.log(x+2) }, 0) }); promise.then(function (value) { console.log(value) }); //输出 ok Uncaught ReferenceError: x is not defined
上面代码中,Promise指定在下一轮“事件循环”再抛出错误,到那个时候,Promise的运行已经结束了,所以这个错误是在Promise函数体外抛出的,会冒泡到最外层,成了未捕获的错误。
一般总是建议,Promise 对象后面要跟catch
方法,这样可以处理 Promise 内部发生的错误。catch
方法返回的还是一个 Promise 对象,因此后面还可以接着调用then
方法。
const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); }); }; someAsyncThing() .catch(function(error) { console.log('oh no', error); }) .then(function() { console.log('carry on'); }); //输出 oh no ReferenceError: x is not defined carry on
上面代码运行完catch
方法指定的回调函数,会接着运行后面那个then
方法指定的回调函数。如果没有报错,则会跳过catch
方法。
例如:
Promise.resolve() .catch(function(error) { console.log('oh no', error); }) .then(function() { console.log('carry on'); }); //输出 carry on
模糊的地方!!!
var someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); }); }; someAsyncThing().then(function() { console.log('2222') return someOtherAsyncThing(); }).catch(function(error) { console.log('oh no', error); // 下面一行会报错,因为 y 没有声明 y + 2; }).then(function() { console.log('carry on'); }); //输出 oh no ReferenceError: x is not defined Uncaught (in promise) ReferenceError: y is not defined
上面代码中,catch
方法抛出一个错误,因为后面没有别的catch
方法了,导致这个错误不会被捕获,也不会传递到外层。
var someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); }); }; someAsyncThing().then(function() { return someOtherAsyncThing(); }).catch(function(error) { console.log('oh no', error); // 下面一行会报错,因为y没有声明 y + 2; }).catch(function(error) { console.log('carry on', error); }); //输出 oh no ReferenceError: x is not defined carry on ReferenceError: y is not defined
3、Promise.prototype.finally()
finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
server.listen(port) .then(function(){ // ... }) .finally(server.stop)
finally方法的回调函数不接受任何参数,这样没办法知道,前面的Promise状态到底是成功 fulfilled 还是 失败rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于Promise的执行结果。
promise .finally(() => { // 语句 }); // 等同于 promise .then( result => { // 语句 return result; }, error => { // 语句 throw error; } );
Promise.prototype.finally = function (callback) { let P = this.constructor; return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) ); };
// resolve 的值是 undefined Promise.resolve(2).then(() => {}, () => {}) // resolve 的值是 2 Promise.resolve(2).finally(() => {}) // reject 的值是 undefined Promise.reject(3).then(() => {}, () => {}) // reject 的值是 3 Promise.reject(3).finally(() => {})
4、Promise.all()
Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例。
const p = Promise.all([p1, p2, p3])
Promise.all()方法接收一个数组作为参数,p1、p2、p3都是Promise实例。如果不是Promise实例,就会调用Promise.resolve方法,将参数转为Promise实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。
p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
注意:如果作为参数的Promise实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all() 的 catch方法
const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result) .catch(e => e); const p2 = new Promise((resolve, reject) => { throw new Error('报错了'); }) .then(result => result) .catch(e => e); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); //输出 ["hello", Error: 报错了]
p1
会resolved
,p2
首先会rejected
,但是p2
有自己的catch
方法,该方法返回的是一个新的 Promise 实例,p2
指向的实际上是这个实例。该实例执行完catch
方法后,也会变成resolved
,导致Promise.all()
方法参数里面的两个实例都会resolved
,因此会调用then
方法指定的回调函数,而不会调用catch
方法指定的回调函数。
如果p2
没有自己的catch
方法,就会调用Promise.all()
的catch
方法。
const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result); const p2 = new Promise((resolve, reject) => { throw new Error('报错了'); }) .then(result => result); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); //输出 Error: 报错了
5、Promise.race()
Promise.race()方法是将多个Promise实例,包装成一个新的Promise实例
const p = Promise.race([p1, p2, p3])
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
栗子:在指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve
var p = Promise.race([ new Promise(function (resolve, reject) { setTimeout(() => resolve(123), 6000) }), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]); p .then(console.log) .catch(console.error); //输出 Error: request timeout
var p = Promise.race([ new Promise(function (resolve, reject) { setTimeout(() => resolve(123), 2000) }), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]); p .then(console.log) .catch(console.error); // 输出 123
6、Promise.resolve()
将现有对象转为Promise对象。参数分为4种情况:
1)参数是一个Promise实例
2)参数是一个thenable对象
3)参数不是具有then方法的对象,或根本不是对象
4)不带有任何参数
7、Promise.reject()
Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。
四、应用
加载图片,可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就会发生变化。
const preloadImage = function (path) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; }) }
Generator函数与Promise的结合
使用Generator函数管理流程,遇到异步操作时,通常返回一个Promise对象。
function getFoo () { return new Promise(function (resolve, reject){ resolve('foo'); }); } const g = function* () { try { const foo = yield getFoo(); console.log(foo); } catch (e) { console.log(e); } }; function run (generator) { const it = generator(); function go(result) { if (result.done) return result.value; return result.value.then(function (value) { return go(it.next(value)); }, function (error) { return go(it.throw(error)); }); } go(it.next()); } run(g); //输出 undefined foo
async()/await()
yuansheng js
webassemble