JavaScript之线程异步阻塞,顺序执行: Promise, Async / await

刚出来不久的 ES8 包含了 async 函数,它的出现,终于让 JavaScript 对于异步操作有了终极解决方案:No more callback hell,不用回调函数来保持异步执行操作的按顺序执行。

一、问题背景:

比如我们需要 按顺序获取:产品数据=>用户数据=>评论数据

传统的写法,无需解释
// 获取产品数据
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 表示暂停且返回(return)其后面的值,
iterator.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

Promise 已经被提及已久了,是 ES6 的一部分。
Promise 能在写法上消除 callback 回调函数内的层层嵌套,相比起来代码更清晰了。

语法:
var promise1 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

console.log(promise1);
// expected output: [object Promise]

promise1.then(successMsg =>{
    console.log(successMsg);
});
// expected output: 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();
  });
}


let myFirstPromise = new Promise((resolve, reject) => {
    // do something
    // ...
    resolve("success!");
});

myFirstPromise.then((successMessage) => {
    console.log("Yay! " + successMessage);
});


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 函数,程式设计如下:
注意:await 的使用!
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('error');
}

e().then(v => console.log(v))
   .catch( e => console.log(e));





与 Promise 结合使用,await 实现线程阻塞,直至 Promise 执行结束。

// 模拟 Ajax,返回一个 Promise
function requestP(url, time) {  
    return new Promise(function(resolve, reject) {   
        setTimeout((time)=>{  
           console.log(`using ${time} ms.`);  
           resolve(url);  
        }, time, time);  
    });  
} 

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

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













-


猜你喜欢

转载自lixh1986.iteye.com/blog/2407596