问题背景:
比如我们需要 按顺序获取:产品数据=>用户数据=>评论数据
传统的写法,无需解释
// 获取产品数据 ajax('products.json', (products) => { console.log('AJAX/products >>>', JSON.parse(products)); // 获取用户数据 ajax('users.json', (users) => { console.log('AJAX/users >>>', JSON.parse(users)); // 获取评论数据 ajax('products.json', (comments) => { console.log('AJAX/comments >>>', JSON.parse(comments)); }); }); });
一、强劲的新朋友 Generators
Generators 是 ES6 一个新的特性,能够 暂停/执行 代码。
yield
The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword.
The yield keyword actually returns an IteratorResult object with two properties, value and done. The value property is the result of evaluating the yield expression, and done is false, indicating that the generator function has not fully completed.
yield 表示暂停且返回一个 IteratorResult 对象。该对象由两个属性组成:value 和 done。其中 value 是 yield 其后面的值(yield在这一点上相当于 return语句),done 表示是否执行完成。
IteratorResult.next() 表示继续执行。
语法一:
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); console.log(g.next()); // {value: 1, done: false} console.log(g.next()); // {value: 2, done: false} console.log(g.next()); // {value: 3, done: true }
语法二:next( ) 带参数
function* gen() { while(true) { var value = yield 10; console.log(value); } } var g = gen(); g.next(1); // "{ value: 10, done: false }" g.next(2); // 2 // "{ value: 10, done: false }"
当程序第二次进入时,会把参数赋值给 value
但是返回值不变!
代码实现:
// Generators function request(url) { ajax(url, (response) => { gen.next(JSON.parse(response)); }); } function* main() { // 获取产品数据 let products = yield request('products.json'); // 获取用户数据 let users = yield request('users.json'); // 获取评论数据 let comments = yield request('comments.json'); console.log('Generator/products >>>', products); console.log('Generator/users >>>', users); console.log('Generator/comments >>>', comments); } var gen = main(); gen.next();
这个实现逻辑上看起来不是那么简介。尤其是 gen.next() 在函数内部、外部都在调用。
二、不算新的朋友 Promise
语法:
new Promise( /* executor */ function(resolve, reject) { ... } );
Promise 被 new 出来之后就会自动执行。
它有两个回调方法:
Promise.then() - Promise 执行成功(resolve)后的回调方法
Promise.catch() - Promise 执行失败(reject)后的回调方法
Promise 已经被提及已久了,是 ES6 的一部分。
Promise 能在写法上消除 callback 回调函数内的层层嵌套,相比起来代码更清晰了。
用法一:
先在控制台输入:
var promise = new Promise(function(resolve, reject) { console.log('当我被new出来后,就已经开始执行了!'); var result = 'foo'; console.log('执行完毕!使用 then() 接收这个参数吧!'); resolve(result); });
过几秒后再输入:
promise.then(successMsg =>{ console.log('接收到了 resolve 方法返回的参数:', successMsg); }); promise.then(successMsg =>{ console.log('继续再用一下 resolve 方法返回的参数:', successMsg); }) .then(successMsg =>{ console.log('此then非彼then:', successMsg); }); // expected output: 接收到 resolve 方法返回的参数:foo
用法二:
resolve : 执行成功时的回调函数
reject:执行失败时的回调函数
// 生产环境下的写法: function myAsyncFunction(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = () => resolve(xhr.responseText); xhr.onerror = () => reject(xhr.statusText); xhr.send(); }); }
用法三:
myFirstPromise = new Promise((resolve, reject) => { // do something // ... // resolve("success!"); reject("failure!"); }); // call back functions myFirstPromise.then(msg => { console.log("Yay! " + msg); }) .catch(msg => { console.log("Opps! " + msg); });
代码实现如下:
Promise的then()方法可以返回另一个Promise,也可以返回一个值。
如果返回的是一个值,它将会被包装成一个Promise。
// Promise // 封装 Ajax,返回一个 Promise function requestP(url, time) { return new Promise(function(resolve, reject) { setTimeout(()=>{resolve(url)}, time); }); } // 获取产品数据 requestP('products.json', 3000).then(function(products){ return products; //此处返回的是一个字符串,但会被包装成一个 Promise 对象。 }) // 获取用户数据 .then((products)=>{ console.log('Promises/products >>>', products); return requestP('users.json', 2000).then(function(users){ return users; }); }) // 获取评论数据 .then((users)=>{ console.log('Promises/users >>>', users); requestP('comments.json', 1000).then(function(comments){ console.log('Promises/comments >>>', comments); }); })
另: Promise.all 的用法
Promise.all 只有所有promise都执行完成后,才会调用 then 函数
Promise.all 不能保证执行的先后顺序,只能保证结果的有序返回。
// Promise // 封装 Ajax,返回一个 Promise function requestP(url, time) { return new Promise(function(resolve, reject) { setTimeout((time)=>{ console.log(`using ${time} ms.`); resolve(url); }, time, time); }); } Promise.all([ requestP('products.json', 3000), requestP('users.json', 2000), requestP('comments.json', 1000) ]) .then(function(data) { // 返回值 data 是一个数组:["xx", "xx", "xx"] console.log('Parallel promises >>>', data); }); /* 输出: using 1000 ms. using 2000 ms. using 3000 ms. Parallel promises >>> (3) ["products.json", "users.json", "comments.json"] */
再看下面的代码:
var promise1 = Promise.resolve(3); var promise2 = 42; var promise3 = new Promise(function(resolve, reject) { setTimeout((t)=>{ console.log(t); resolve(t);}, 3000, 'foo3'); }); var promise4 = new Promise(function(resolve, reject) { setTimeout((t)=>{ console.log(t); resolve(t);}, 2000, 'foo2'); }); var promise5 = new Promise(function(resolve, reject) { setTimeout((t)=>{ console.log(t); resolve(t);}, 1000, 'foo1'); }); Promise.all([promise1, promise2, promise3, promise4, promise5]).then(function(values) { console.log(values); }); /* expected output: > "foo1" > "foo2" > "foo3" > Array [3, 42, "foo3", "foo2", "foo1"] */
我们期望 foo3 应该在 foo1 的前面执行,而事实相反。
分析:
Promise.all 只能保证结果的输出顺序。
Promise.all 并不能保证 promise 的执行顺序,
三、 压轴出场的 async 函数 + await
如果要做到顺序执行,需要用到 async 函数。
async:
异步执行函数,内部使用 await + Promise 对象控制执行顺序。
注意:其返回值也是一个Promise对象,这样该async函数可以在其它async函数中被使用。
await:
只能用在 async 函数内部,await 后面往往跟一个Promise对象。
await 用于等待跟在其后的 Promise 对象执行完毕,获取并返回Promise执行的结果。
程式设计如下:
async function executeSequentially() { const tasks = [fn1, fn2, fn3] for (const fn of tasks) { await fn() } }
示例代码:
var tasks = [ _ => new Promise(res => setTimeout(_ => res("1"), 1000)), _ => new Promise(res => setTimeout(_ => res("2"), 1000)), _ => new Promise(res => setTimeout(_ => res("3"), 1000)), _ => new Promise(res => setTimeout(_ => res("4"), 1000)), _ => new Promise(res => setTimeout(_ => res("5"), 1000)), _ => new Promise(res => setTimeout(_ => res("6"), 1000)), _ => new Promise(res => setTimeout(_ => res("7"), 1000)) ]; (async (promises)=>{ for (let promise of promises) { console.log(await promise()); } })(tasks); /* Output: 1 2 3 4 5 6 7 */
思考:为什么不写成这种形式?
var tasks = [ new Promise(res => setTimeout(_ => res("1"), 1000)), new Promise(res => setTimeout(_ => res("2"), 1000)), new Promise(res => setTimeout(_ => res("3"), 1000)), new Promise(res => setTimeout(_ => res("4"), 1000)), new Promise(res => setTimeout(_ => res("5"), 1000)), new Promise(res => setTimeout(_ => res("6"), 1000)), new Promise(res => setTimeout(_ => res("7"), 1000)) ]; (async (promises)=>{ for (let promise of promises) { console.log(await promise); } })(tasks);
分析:
将 Promise 封装在一个函数中
_ => new Promise(res => setTimeout(_ => res("1"), 1000))
只有该函数执行时才会去 new Promise。
因为 Promise 一旦 new 出来后,便会自动执行。所以不要提前去 new。
语法:
1、async 函数返回一个 Promise 对象。return 的值,会成为 then 方法回调函数的参数。
async function f() { return 'hello world'; } f().then( (v) => console.log(v)) // hello world
2、async 函数会挨个等,等到内部所有 await 的 Promise 对象执行完,才会执行 then 方法。
const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout)); async function f(){ await delay(1000); await delay(2000); await delay(3000); return 'done'; } f().then(v => console.log(v)); // 等待6s后才输出 'done'
3、正常情况下,await 命令后面跟一个 Promise 对象。如果不是的话,也会被转换成一个 立即 resolve 的 Promise 对象。
async function f() { return await 1; }; f().then( (v) => console.log(v)) // 1
4、异常处理:一、如果 async 函数内部抛出异常,则返回的 Promise 对象状态为 reject 状态。异常可被 catch 方法中的回调函数接收到。
async function e(){ throw new Error('sorry, error happens!!'); } e().catch( e => console.log(e)); // output: // Error: sorry, error happens!!
5、异常处理:二、如果 async 函数内部使用 await 的 Promise 对象状态为 reject,该异常也可被 async 函数的 catch 方法中的回调函数接收到。(根据异常的冒泡捕获机制)
async function asyncCall() { var result = await new Promise((resolve, reject) => { setTimeout(() => { reject('rejected.'); }, 2000); }); console.log(result); } asyncCall().catch(function(rejectedResult){ console.log('Exception:', rejectedResult); }); // output: // "Exception:" "rejected."
最终代码
与 Promise 结合使用,await 实现线程阻塞,直至 Promise 执行结束。
// 模拟 Ajax,返回一个 Promise function requestP(url, time) { callback = (resolve, reject) => { setTimeout((param)=>{ console.log(`using ${param} ms.`); resolve(url); }, time, time); } return new Promise(callback); } (async () => { // 获取产品数据 let products = await requestP('products.json', 3000); // 获取用户数据 let users = await requestP('users.json', 2000); // 获取评论数据 let comments = await requestP('comments.json', 1000); console.log('ES7 Async/products >>>', products); console.log('ES7 Async/users >>>', users); console.log('ES7 Async/comments >>>', comments); })() .catch(err => { console.log(err); }); /* Output: using 3000 ms. using 2000 ms. using 1000 ms. ES7 Async/products >>> products.json ES7 Async/users >>> users.json ES7 Async/comments >>> comments.json */
引用:
https://github.com/jaydson/es7-async
http://blog.csdn.net/sinat_17775997/article/details/60609498
http://blog.csdn.net/qq673318522/article/details/75331225
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/async_function
-