本文摘自:
JavaScript Promise 对象 www.runoob.com/w3cnote/jav…
蒋先生 : 「长文干货」Promise和Async/await的理解和使用 www.cnblogs.com/jiangweiche…
promise
ECMAscript 6 原生提供了 Promise 对象。
Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。
Promise 对象有以下两个特点:
1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:
pending(待决定的): 初始状态,不是成功或失败状态。
fulfilled(执行): 意味着操作成功完成。
rejected(拒绝): 意味着操作失败。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise 基本流程
Promise 优缺点
优点: 有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
缺点: 首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise 创建
new一个Promise实例化对象
let promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});
Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。
promise.then() , promise.catch() 是 promise 最为常用的方法。
let myFirstPromise = new Promise(function(resolve, reject){
//当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
//在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
setTimeout(function(){
resolve("成功!"); //代码正常执行!
}, 250);
}).then(function(successMessage){
//successMessage的值是上面调用resolve(...)方法传入的值.
//successMessage参数不一定非要是字符串类型,这里只是举个例子
document.write("Yay! " + successMessage);
}).catch(err){
console(err)
}
上面代码中,resolve 方法和 reject 方法调用时,都带有参数。它们的参数会被传递给回调函数。reject 方法的参数通常是 Error 对象的实例,而 resolve 方法的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。
let p1 = new Promise(function(resolve, reject){
// ... some code
});
let p2 = new Promise(function(resolve, reject){
// ... some code
resolve(p1);
})
上面代码中,p1 和 p2 都是 Promise 的实例,但是 p2 的 resolve 方法将 p1 作为参数,这时 p1 的状态就会传递给 p2。如果调用的时候,p1 的状态是 pending,那么 p2 的回调函数就会等待 p1 的状态改变;如果 p1 的状态已经是 fulfilled 或者 rejected,那么 p2 的回调函数将会立刻执行。
为什么要用 Promise?
指定回调函数的方式更加灵活
旧的:回调函数必须在启动异步任务前指定
Promise:启动异步任务 => 返回 Promise 对象 => 给 Promise 对象绑定回调函数,甚至可以在异步任务结束后指定多个
支持链式调用,可以解决回调地狱问题
什么是回调地狱?回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回掉执行条件,代码是水平向右扩展
// 回调地狱
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult)
}, failureCallback)
}, failureCallback)
},
回调地狱的缺点:不便阅读,不便于异常处理 ;
解决方案:Promise 链式调用,代码水平向下扩展
doSomething().then(function(result) {
return doSomethingElse(result)
})
.then(function(newResult) {
return doThirdThing(newResult)
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult)
})
.catch(failureCallback)
终极解决方案:async/await,用同步的写法处理异步的操作
async function request() {
try {
const result = await doSomething()
const newResult = await doSomethingElse(result)
const finalResult = await doThirdThing(newResult)
console.log('Got the final result: ' + finalResult)
} catch (error) {
failureCallback(error)
}
}
如何改变 Promise 的状态
resolve(value),如果当前是 pendding 就会变为 resolved
reject(reason),如果当前是 pendding 就会变为 rejected
抛出异常,如果当前是 pendding 就会变为 rejected
const p = new Promise((resolve, reject) => {
// resolve(1) // Promise 变为 resolved 成功状态
// reject(2) // Promise 变为 rejected 失败状态
// Promise 变为 rejected 失败状态,reason为抛出的 error
throw new Error('我抛出的异常')
// 变为 rejected 失败状态,reason为抛出的 3
// throw 3
})
p.then(
value => {},
reason => { console.log('reason :', reason); }
)
Promise.prototype.then方法
下面代码使用 then 方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。如果前一个回调函数返回的是Promise对象,这时后一个回调函数就会等待该Promise对象有了运行结果,才会进一步调用。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// proceed
});
Promise.prototype.catch方法
Promise.prototype.catch 方法是 Promise.prototype.then(null, rejection) 的别名,用于指定发生错误时的回调函数。
Promise 对象的错误具有"冒泡"性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。
getJSON("/posts.json").then(function(posts) {
// some code
}).catch(function(error) {
// 处理前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前两个回调函数的错误
});
Promise.all方法,Promise.race方法
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口(iterator 接口详见 => zhuanlan.zhihu.com/p/82299041)… Promise 实例。)
let p = Promise.all([p1,p2,p3]);
(1)只有p1、p2、p3的状态都变成fulfilled(执行),p的状态才会变成fulfilled(执行),此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected(拒绝),p的状态就变成rejected(拒绝),此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。
如果Promise.all方法和Promise.race方法的参数,不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。
let p = Promise.race([p1,p2,p3]);
Promise.resolve 方法,Promise.reject 方法
Promise.resolve 方法可以将现有对象转为Promise对象,Promise.resolve 方法就起到这个作用。
let jsPromise = Promise.resolve($.ajax('/whatever.json'));
上面代码将 jQuery 生成 deferred 对象(deferred 对象 详见=> www.ruanyifeng.com/blog/2011/0… 转为一个新的 ES6 的 Promise 对象。
如果 Promise.resolve 方法的参数,不是具有 then 方法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为fulfilled。
Promise Ajax
用 Promise 对象实现的 Ajax
resolve 方法和 reject 方法调用时,都带有参数。它们的参数会被传递给回调函数。reject 方法的参数通常是 Error 对象的实例
function ajax(URL) {
return new Promise(function (resolve, reject) {
let req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
let URL = "/try/ajax/testpromise.php";
ajax(URL).then(function onFulfilled(value){
document.write('内容是:' + value);
}).catch(function onRejected(error){
document.write('错误为:' + error);
async 与 await
Async/await是基于 promises 而成的 ,Async/await 实际上只是 promises 的语法糖
async function 用来定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果,。如果你在代码中使用了异步函数,就会发现它的语法和结构会更像是标准的同步函数。
await 操作符用于等待一个 Promise 对象。它只能在异步函数 async function 中使用。
async function fn1() {
return 1
}
const result = fn1()
console.log(result) // Promise { 1 }
可以看到控制台输出了一个promise对象
既 Promise 对象,那么我们用 then 来调用,并抛出错误,执行 onRejected() 且 reason 为错误信息为“快乐的抛出一堆错误”
async function fn1() {
// return 1
// return Promise.resolve(1)
// return Promise.reject(2)
throw '快乐的抛出一堆错误'
}
fn1().then(
value => { console.log('onResolved()', value) },
reason => { console.log('onRejected()', reason) } // onRejected() 快乐的抛出一堆错误
)
await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
如果表达式是 promise 对象, await 返回的是 promise 成功的值
如果表达式是其它值, 直接将此值作为 await 的返回值
await 必须写在 async 函数中, 但 async 函数中可以没有 await,如果 await 的 Promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理