`

JavaScript之ECMAScript6新特性之_02_线程异步阻塞: 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
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) { ... } );

Promisenew 出来之后就会自动执行。
它有两个回调方法:
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













-


分享到:
评论

相关推荐

    前端面试题汇总_madeaef_html/css/javascript_前端面试题汇总_

    - 异步编程:包括回调函数、Promise和async/await,处理非阻塞代码执行。 - ES6及后续版本的新特性:箭头函数、模板字符串、let和const、解构赋值、类和模块等。 - AJAX:异步JavaScript和XML,用于实现页面无...

    深入理解JavaScript的async/await

    随着ES6(ECMAScript 2015)的到来,JavaScript引入了Promise来更好地处理异步操作,而async/await则是基于Promise的进一步改进,旨在让异步代码的书写和理解更接近同步代码。 async关键字用于声明一个异步函数。当...

    异步函数同步_Make_an_asynchronous_function_synchronous_javascript

    在JavaScript编程中,异步处理是一项核心特性,它允许代码在等待I/O操作(如文件读写、网络请求)时不会阻塞程序的执行。然而,有时开发者可能需要将异步函数转换为同步形式,以便在特定场景下更容易控制流程。本文...

    JavaScript语言精粹(修订版)

    4. **异步编程**:JavaScript是单线程的,但通过事件循环和回调函数、Promise、async/await等方式实现了异步处理,避免了阻塞UI的问题。 5. **模块化**:ES6引入了模块系统,通过import和export关键字,使得代码...

    【第九章】JavaScript【ECMAScript语法基础】

    JavaScript是非阻塞的事件驱动模型,主要通过回调函数、Promise和async/await进行异步操作。Promise解决了回调地狱问题,async/await则提供了更清晰的异步编程语法。 九、模块化 ES6引入了模块系统,使用`import`和...

    DeveloperProfile:我的简历

    7. **异步编程**:JavaScript中的Promise、async/await用于解决回调地狱问题,提高了异步编程的可读性和可维护性。理解事件循环和回调队列是掌握异步编程的关键。 8. **测试与调试**:开发者应熟练使用Mocha、Jest...

    JavaScript JavaScript

    ECMAScript 6(简称ES6)引入了许多新特性,如箭头函数、类、模板字符串、解构赋值、let和const、生成器等,提高了代码的简洁性和可读性。 **第十章:Promise与Async/Await** Promise和async/await是ES6引入的异步...

    JavaScript网页开发:体验式学习教程_0

    同时,异步编程,如回调函数、Promise和async/await,是处理非阻塞I/O操作的关键,尤其是在处理网络请求和定时任务时。 ES6(ECMAScript 2015)及后续版本引入了许多新特性,如类和模块系统,这使得JavaScript的...

    JavaScript_实_例_精_通

    ECMAScript是JavaScript的标准,ES6(2015)引入了许多新特性,如箭头函数、解构赋值、类(class)、let/const、Promise、模板字符串等。后续版本如ES7、ES8、ES9、ES10等持续添加新功能,如async/await、Object ...

    Javascript(一).ppt.zip_javascript

    除此之外,异步编程也是JavaScript的一大特点,主要包括回调函数、Promise和async/await。异步编程解决了JavaScript单线程环境下处理耗时操作(如网络请求、文件读写)的问题,避免了程序阻塞。例如,我们可以使用`...

    javascript_guideBook.rar

    7. **ES6及后续版本新特性**:ECMAScript 6引入了许多新特性,如箭头函数、模板字符串、类、模块、解构赋值等,后续版本如ES7、ES8等也不断添加新功能,如Async/Await、Promise.all()等。 8. **AJAX与Fetch API**:...

    JavaScript权威指南(JavaScript犀牛书一本)

    4. **异步编程**:详述了事件循环、回调函数、Promise、async/await等异步处理方式,帮助开发者应对JavaScript的非阻塞I/O特性。 5. **正则表达式**:介绍了JavaScript中的正则表达式语法和用法,用于字符串的匹配...

    yoctolib_es2017:Yoctopuce官方EcmaScript 2017库

    它利用Promise对象处理与设备的异步通信,并利用新的EcmaScript 2017 async / await非阻塞语法实现异步I / O。 令人高兴的是,2017年就已经到了,大多数Javascript引擎中都提供了开箱即用的async / await功能。 不...

    第11章 JavaScript的应用_javascript_

    6. **ES6及新特性**:ECMAScript 6(ES6)引入了类、箭头函数、模板字符串、解构赋值等新特性,提升了JavaScript的可读性和编写效率。后续的ES7、ES8等版本持续引入了更多的语言特性,如async/await、Promise.all()...

    JavaScript学习指南 高清 PDF

    4. **异步编程**:JavaScript是单线程的,但通过事件循环和回调函数、Promise、async/await等方式处理异步操作,确保了非阻塞的程序执行。 5. **DOM操作**:DOM(文档对象模型)是HTML和XML文档的结构表示,...

    阿里云osssdk支持es7asyncawait

    `es7 async/await` 是ECMAScript 2017(ES7,实际上发布时称为ES8)引入的一个特性,它允许我们以同步的方式编写异步代码。`async` 关键字用来声明一个异步函数,这个函数会返回一个Promise。`await` 关键字只能在`...

    JavaScript源码大全

    5. **异步编程**:JavaScript通过回调函数、Promise、async/await等方式支持非阻塞的异步编程,提高程序的执行效率。 6. **ECMAScript规范**:JavaScript的标准化由ECMAScript规范定义,每隔几年会发布新的版本,如...

    JavaScript_LangServe.zip

    6. 异步编程:JavaScript是单线程的,但是通过事件循环、回调函数、Promise和async/await等方式实现了异步处理,使得程序可以非阻塞地执行。 7. DOM操作:JavaScript可以与HTML文档对象模型(DOM)交互,添加、删除...

    JavaScript练习JavaScript练习JavaScript练习

    4. **异步编程**:JavaScript是单线程语言,但通过事件循环和回调函数、Promise、async/await等方式处理异步任务。理解事件队列和宏任务、微任务的概念,能帮助开发者编写更高效的代码。 5. **DOM操作**:...

Global site tag (gtag.js) - Google Analytics