概述
promise
是异步编程的一种解决方案,比传统的解决方案—回调函数和事件—更合理更强大。
所谓的promise
就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作的结果)。
Promise
是一个对象,从它这里可以获取异步操作的信息, Promise
提供统一的API,各种异步操作都可以用同样的方法处理。
promise
的特点:
-
对象的状态不受外界影响,
promise
对象代表一个异步操作,有三种状态:pending(进行中)
、Fulfilled(已成功)
、Rejected(已失败)
。只有异步操作的结果才可以决定当前是哪一种操作状态,任何其他操作都无法改变这种状态。 -
一旦状态改变就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变只有两种可能:从pending(进行中)
变为Fulfilled(已成功)
、或者从pending(进行中)
变成Rejected(已失败)
。状态发生改变就凝固了,不会再变,而是一直保持这个结果,这是就称为Resolved(已定型)
。就算改变已经发生,再对Promise
对象添加回调函数,也会立即得到这个结果,这个与event
完全不同,事件的特点是,如果错过了它,再去监听是得不到结果的。有了
promise
对象,就可以将异步操作以同步操作表示出来,避免了层层嵌套的回调函数。Promise
的缺点:-
无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 -
如果不设置回调函数,
Promise
内部抛出错误不会反应到外部。 -
当处于
Pending
状态时,无法得知目前进展到哪一个阶段了。
-
let promise = new Promise(function(res, rej) { // your code... }) promise.then(function(value) { // res }, function(error) { // rej })
let promise = new Promise(function(resolve, reject) { // resolve, reject 才是真正的异步 console.log('1') resolve() }) promise.then(() => { console.log('2') }) console.log('3') // 1 3 2
// 异步加载图片 function loadImageAsync(url) { return new Promise(function(resolve, reject) { let image = new Image() iamge.onload = function () { resolve(image) } image.onerror = function() { reject(new Error('不能加载' + url)) } iamge.src = url }) }
// promise对象实现AJAX // getJSON 是对XMLHttpReqest对象的封装,用于发出一个JSON数据的HTTP请求 // 并返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数 // 都带有参数 let getJSON = function(url) { let promise = new Promise(function(resolve, reject) { let client = new XMLHttpRequest() client.open('GET', url) client.onreadystatechange = handler client.responseType = "json" client,setRequestHeader('Accept','application/json') client.send() function handler() { if(this.readyState !== 4) { return } if(this.status === 200) { resolve(this.response) } else { reject(new Error(this.statusText)) } } }) return promise } getJSON('/posts.json').then(function(json) { console.log('Contents:' + json) },function(error) { console.log('error',error) })
/** * 如果resolve 和 reject 函数都带有参数,那么这些参数会被传递到回调函数。 * reject函数接收Error对象的实例,表示抛出的错误 * reslove 函数参数除了正确的值,还有可能是一个Promise对象 */ let p1 = new Promise(function(resolve, reject) { // ... }) let p2 = new Promise(function(resolve, reject) { // ... resolve(p1) }) /**p1 和 p2 都是Promise的实例,但是p2的resolve方法将p1作为参数 * 即一个异步操作的结果是返回另一个异步操作。 * p1的状态传递给p2.p1的状态决定了p2的状态 */
let p1 = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }) let p2 = new Promise(function(resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2.then(result => console.log(result)).catch(error => console.log(error)) /** * p1 是 一个Promise,3s之后变成rejected。 * p2 的状态在1s后改变,resolve方法返回的是p1 */
promise.then(function(value) { // res }, function(error) { // rej }) // then方法有两个参数,第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。 // then返回的是一个新的Promise实例
Promise.prototype.catch
方法是.then(null, rejection)
的别名,用于指定发生错误的回调函数。
另外then
方法指定的回调函数如果在运行中抛出错误,也会被catch
捕获。
一般来说,不要在then
p2.then(result => console.log(result)) .catch(error => console.log(error)) // .catch 处理 p2 和前一个回调函数运行时发生的错误 // ====> 等同于 p2.then(result => console.log(result)) .then(null, err => console.log(err))
Promise.all()
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]); // Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法, 将参数转为 Promise 实例,再进一步处理。
const databasePromise = connectDatabase(); const booksPromise = databasePromise.then(findAllBooks); const userPromise = databasePromise.then(getCurrentUser); // booksPromise和userPromise是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommentations这个回调函数。 // 如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法 Promise.all([booksPromise, userPromise]).then(([books, user]) => pickTopRecommentations(books, user));
async函数
ES7标准引入了async
函数,使得异步操作变得简单,——async
就是Generator
的语法糖。
用法
async
函数返回一个Promise
对象,可以使用then
方法添加回调函数。当函数执行的时候,
async function getStockPriceByName(name) { const symbol = await getStockSymbol(name); const stockPrice = await getStockPrice(symbol); return stockPrice; } getStockPriceByName('goog').then(function (result) { console.log(result); }); // 1、函数前面的async关键字表明该函数内部有异步操作。 // 2、调用该函数会立即返回一个Promise对象
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { // 一旦遇上await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。 await timeout(ms); console.log(value); } asyncPrint('hello world', 3000); // 3s之后返回 'hello world' /** ------------- 改写 ------------------ */ async function timeout(ms) { await new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 3000);
语法
async
函数返回一个promise
对象,async
函数内部return语句返回的值,会成为then
方法回调函数的参数。
Promise 对象的状态变化
async
函数返回的 Promise
对象,必须等到内部所有await
命令后面的 Promise
对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。
也就是说,只有async
函数内部的所有异步操作执行完,才会执行then
方法指定的回调函数。
正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。
async function f() { return await 123; } // await命令的参数是数值123,它被转成 Promise 对象,并立即resolve。 f().then(v => console.log(v)) // 123
await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。
async function f() { await Promise.reject('出错了'); } f().then(v => console.log(v)) .catch(e => console.log(e)) // 出错了
只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
async function f() { await Promise.reject('出错了'); await Promise.resolve('hello world'); // 不会执行 } // 第二个await不会执行
async function f() { try { await Promise.reject('出错了'); } catch (e) { console.log(e); } return await Promise.resolve('hello world'); } f().then(v => console.log(v)) // hello world
async function f() { await Promise.reject('出错了').catch(e => console.log(e)); return await Promise.resolve('hello world'); } f().then(v => console.log(v)) // 出错了 // hello world
错误处理
如果await后面的异步操作出错了,那么等同于async函数返回的Promise对象被reject了。
async function f() { await new Promise(function (resolve, reject) { throw new Error('出错了'); }); } f().then(v => console.log(v)).catch(e => console.log(e)) // Error:出错了 // async函数f执行后,await后面的 Promise 对象会抛出一个错误对象, // 导致catch方法的回调函数被调用,它的参数就是抛出的错误对象。
// 有多个await命令,则可以统一放在try...catch...结构中 async function main() { try { const val1 = await firstStep(); const val2 = await secondStep(val1); const val3 = await thirdStep(val1, val2); console.log('Final: ', val3); } catch (err) { console.error(err); } }
await使用注意点
第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以好把await命令放在try...catch代码块中。
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一种写法 async function myFunction() { await somethingThatReturnsAPromise().catch(function (err) { console.log(err); }); }
第二点,多个await命令后面的异步操作,如果不存在继发关系,好让它们同时触发
// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); } // 如果将forEach方法的参数改成async函数,也有问题。 function dbFuc(db) { //这里不需要 async let docs = [{}, {}, {}]; // 可能得到错误结果 docs.forEach(async function (doc) { await db.post(doc); }); } // 原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。
async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }
// 如果确实希望多个请求并发执行,可以使用Promise.all方法。当三个请求都会resolved时,下面两种写法效果相同。 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } // 或者使用下面的写法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }
实例
var pro = new Promise(function(){ var a = 1; }) console.log(pro);
const fs = require('fs') // 总结:只要 new 了一个具体的异步操作,这个异步操作被创建的一瞬间,就会立即执行; function readFileByPath(fpath) { const p = new Promise(function() { fs.readFile(fpath, 'utf-8', (err, result) => { if (err) return console.log('读文件失败:' + err.message) console.log(result) }) }) } readFileByPath('./files/3.txt') // 文件必须存在
使用.then()进一步封装读文件的操作
封装原则:不要在方法内部显示结果,要把结果返回给调用者,不要提调用者做决定!!
const fs = require('fs') function readFileByPath(fPath) { return new Promise(function(resolve,reject){ fs.readFile(fPath, 'utf-8', (err, result) => { if (err) return reject(arr) resolve(result) }) }) } // 一般.then()方法中,失败的回调可以省略,但是省略以后读取文件失败时,无法接收结果 // 这是,我们可以使用.catch() 来指定失败的回调 /* --------------读一个-------------------- */ readFileByPath('./files/1.txt') .then(function(result) { console.log(result) }) .catch(err => console.log(err.message)) /* ---------------读多个----------------------- */ readFileByPath('./files/1.txt') .then(function(result){ console.log(result) return readFileByPath('./files/2.txt') }) .then(function(result){ console.log(result) return readFileByPath('./files/3.txt') }) .catch(err => console.log(err.message))
const fs = require('fs') function readFileByPath(fPath) { return new Promise(function(resolve,reject){ fs.readFile(fPath, 'utf-8', (err, result) => { if (err) return reject(arr) resolve(result) }) }) } // async 用来修饰异步方法 // await 只能用在被 async 修饰的方法中 // 同时,await 是用来修饰 Promise 实例对象的;简化promise对象 console.log("开始"); async function readAll () { console.log("方法头部"); const result1 = await readFileByPath('./files/1.txt') console.log(result1) const result2 = await readFileByPath('./files/2.txt') console.log(result2) const result3 = await readFileByPath('./files/3.txt') console.log(result3) console.log("方法尾部"); } console.log("结束"); readAll()
运行顺序:
原因: