概述

promise是异步编程的一种解决方案,比传统的解决方案—回调函数和事件—更合理更强大。

所谓的promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作的结果)。

Promise是一个对象,从它这里可以获取异步操作的信息, Promise提供统一的API,各种异步操作都可以用同样的方法处理。

promise的特点:

  1. 对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态:pending(进行中)Fulfilled(已成功)Rejected(已失败)。只有异步操作的结果才可以决定当前是哪一种操作状态,任何其他操作都无法改变这种状态。

  2. 一旦状态改变就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变只有两种可能:从pending(进行中)变为Fulfilled(已成功)、或者从pending(进行中)变成Rejected(已失败)。状态发生改变就凝固了,不会再变,而是一直保持这个结果,这是就称为Resolved(已定型)。就算改变已经发生,再对Promise对象添加回调函数,也会立即得到这个结果,这个与event完全不同,事件的特点是,如果错过了它,再去监听是得不到结果的。

    有了promise对象,就可以将异步操作以同步操作表示出来,避免了层层嵌套的回调函数。

    Promise的缺点:

    1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消。

    2. 如果不设置回调函数,Promise内部抛出错误不会反应到外部。

    3. 当处于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
})
}

模拟AJAX

// 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)
})

promise中的promise

/**
* 如果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.prototype.then()

作用:为了Promise实例添加状态改变时的回调。

promise.then(function(value) {
// res
}, function(error) {
// rej
}) // then方法有两个参数,第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。
// then返回的是一个新的Promise实例

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误的回调函数。

另外then方法指定的回调函数如果在运行中抛出错误,也会被catch捕获。

一般来说,不要在then方法中定义Rejected状态的回调(即then的第二个参数),而应该总是使用catch方法。

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 实例,再进一步处理。

p的状态由p1、p2、p3决定,分成两种情况。

1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

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方法添加回调函数。当函数执行的时候,

一旦遇上await就会先返回,等到异步操作完成,再接着执行函数体内后面

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)) //

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不会执行

现在,需要在第一个异步操作失败,也不要中断后面的异步操作。

这是可以将第一个await放在try...catch...结构里面,这样不管这个异步操作返回的结果是resolve还是reject都会执行之后的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

还有一种方法,就是在第一个await之后使用catch接收失败的回调,处理前面出现的错误。

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()进一步封装读文件的操作

封装原则:不要在方法内部显示结果,要把结果返回给调用者,不要提调用者做决定!!

使用.catch()进一步封装读文件的操作

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))

async和await

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()

运行顺序:

原因:

/* async修饰的 readAll()方法异步方法,在调用这个readAll()方法的时候,js主线程进入这个方法
这时,还是主线程在执行,输出“方法头部”,然后在 await是异步操作,这是主线程就退出这个方法,
执行“结束”,然后就是异步的方法顺序执行 */

最新文章

  1. eclipse内嵌jetty(run-jetty-run插件) 配置jndi数据源
  2. Echarts-画堆积柱状图
  3. eclipse自动部署问题
  4. 关于jQuery的inArray 方法介绍
  5. C#的输入、输出与运算符、数据类型
  6. dispay属性的block,inline,inline-block
  7. php上传图片---初级版
  8. (ORA-12899) 10g数据库导入到11g数据库时报错
  9. 【匈牙利算法】 二分图模板 poj 1274
  10. VB6之反编译工具VBRezQ
  11. zzuli 1815: easy problem 打表
  12. 简介CentOS与 Ubuntu的不同
  13. URLs ...
  14. Java容器解析系列(2) 具体化的第一步——Collection到AbstractCollection
  15. Linux内核分析 读书笔记 (第十八章)
  16. 【转】GPS定位准确度CEP、RMS
  17. insert into on duplicate key update
  18. Google Hack的一些整理
  19. 判断当前用户有无Administrator的权限
  20. [Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'number primary key,

热门文章

  1. 017、Java中使用float型
  2. Codeforces 459E Roland and Rose
  3. oracle进入CDB
  4. Android studio 中出现fail to load platform rendering library
  5. Web系统测试的常用方法总结-18《转载》
  6. IPv6-isis配置
  7. 用25行JavaScript语句实现一个简单的编译器
  8. 140-PHP类的抽象方法和继承
  9. 了解facade设计模式
  10. Meeloun教你如何正式切入Essay写作话题