金沙棋牌官方平台

当前位置:金沙棋牌 > 金沙棋牌官方平台 > 异步处理技术方案,异步函数现已正式可用

异步处理技术方案,异步函数现已正式可用

来源:http://www.logblo.com 作者:金沙棋牌 时间:2019-11-29 12:45

要点和细节

相信我们已经感受到了 asyns 和 await 的美妙之处,接下来让我们深入了解一下细节:

  • async 和 await 建立在 Promise 之上。使用 async,总是会返回一个 Promise。请记住这一点,因为这也是容易犯错的地方。
  • 当执行到 await 时,程序会暂停当前函数,而不是所有代码
  • async 和 await 是非阻塞的
  • 依旧可以使用 Promise helpers,例如 Promise.all( )

正如之前的示例:

async function logPosts () { try { let user_id = await fetch('/api/users/username') let post_ids = await fetch('/api/posts/<code>${user_id}') let promises = post_ids.map(post_id => { return fetch('/api/posts/${post_id}') } let posts = await Promise.all(promises) console.log(posts) } catch (error) { console.error('Error:', error) } }</code>

1
2
3
4
5
6
7
8
9
10
11
12
13
async function logPosts ()  {
    try {
        let user_id = await fetch('/api/users/username')
        let post_ids = await fetch('/api/posts/<code>${user_id}')
        let promises = post_ids.map(post_id => {
            return  fetch('/api/posts/${post_id}')
        }
        let posts = await Promise.all(promises)
        console.log(posts)
    } catch (error) {
        console.error('Error:', error)
    }
}</code>
  • await 只能用于声明为 async 的函数中
  • 因此,不能在全局范围内使用 await

如下代码:

// throws an error function logger (callBack) { console.log(await callBack) } // works! async function logger () { console.log(await callBack) }

1
2
3
4
5
6
7
8
9
// throws an error
function logger (callBack) {
    console.log(await callBack)
}
 
// works!
async function logger () {
    console.log(await callBack)
}

异步generator函数

简单的说就是async与generator函数的结合,如下

async function* gen() {
  yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }

首先genObj.next()返回的是一个promise,然后调用then,执行then里面的函数,再看一个我认为比较重要的例子

async function* readLines(path) {
let file = await fileOpen(path);
try {
  while (!file.EOF) {
  yield await file.readLine();
}
} finally {
  await file.close();
}
}

我们可以看出异步generator函数,既有await也有yield,其中await命令用于将file.readLine()返回的结果输入到函数内,然后yield用于将输入到函数内的结果从函数输出


在异步generator函数中的yield* 语句后面还可以跟一个异步的generator函数

Promise 其他的方法

Promise.all(当所有在可迭代参数中的 promises 已完成,或者第一个传递的 promise(指 reject)失败时,返回 promise。)

var p1 = Promise.resolve(3); var p2 = 1337; var p3 = new Promise((resolve, reject) => {  setTimeout(resolve, 100, "foo"); }); Promise.all([p1, p2, p3]).then(values => {  console.log(values); // [3, 1337, "foo"] });

Promise.race(返回一个新的 promise,参数 iterable 中只要有一个 promise 对象 “ 完成(resolve)” 或 “ 失败(reject)”,新的 promise 就会立刻 “ 完成(resolve)” 或者 “ 失败(reject)”,并获得之前那个 promise 对象的返回值或者错误原因。)

var p1 = new Promise(function(resolve, reject) {    setTimeout(resolve, 500, "one"); }); var p2 = new Promise(function(resolve, reject) {    setTimeout(resolve, 100, "two"); }); Promise.race([p1, p2]).then(function(value) {    console.log(value); // "two"    // 两个都完成,但 p2 更快 });

有趣的是如果你使用 ES6 的 class,你是可以去派生 Promise 的。

class MePromise extends Promise{  // 处理 ... }

同步循环中的异步等待

某些情况下,你想要在同步循环中调用异步函数。例如:

async function process(array) { for (let i of array) { await doSomething(i); } }

1
2
3
4
5
async function process(array) {
  for (let i of array) {
    await doSomething(i);
  }
}

不起作用,下面的代码也一样:

async function process(array) { array.forEach(async i => { await doSomething(i); }); }

1
2
3
4
5
async function process(array) {
  array.forEach(async i => {
    await doSomething(i);
  });
}

循环本身保持同步,并且总是在内部异步操作之前完成。

ES2018 引入异步迭代器,除了 next() 方法返回一个 Promise 对象之外,与常规迭代器类似。因此,await 关键字可以与 for ... of 循环一起使用,以串行方式运行异步操作。例如:

async function process(array) { for await (let i of array) { doSomething(i); } }

1
2
3
4
5
async function process(array) {
  for await (let i of array) {
    doSomething(i);
  }
}

然而,在异步迭代器实现之前,最好的方案是将数组每项 mapasync 函数,并用 Promise.all() 执行它们。例如:

const todo = ['a', 'b', 'c'], alltodo = todo.map(async (v, i) => { console.log('iteration', i); await processSomething(v); }); await Promise.all(alltodo);

1
2
3
4
5
6
7
8
const
  todo = ['a', 'b', 'c'],
  alltodo = todo.map(async (v, i) => {
    console.log('iteration', i);
    await processSomething(v);
});
 
await Promise.all(alltodo);

这样有利于执行并行任务,但是无法将一次迭代结果传递给另一次迭代,并且映射大数组可能会消耗计算性能。

Promise 改善了这种情况

这正是 Promise 的优势所在,Promise 是对还未产生的数据的一种推理。Kyle Simpson 将 Promise 解释为:就像在快餐店里点餐一样。

  • 点餐
  • 为所点的午餐付费,并拿到排队单号
  • 等待午餐
  • 当你的午餐准备好了,会叫你的单号提醒你取餐
  • 收到午餐

正如上面的这种场景,当你等餐时,你是无法吃到午餐的,但是你可以提前为吃午餐做好准备。你可以进行其它事情,此时你知道午餐就要来了,虽然此刻你还无法享用它,但是这个午餐已经“promise”给你了。这就是所谓的 promise,表示一个最终会存在的数据的对象。

readFile(file1) .then((file1-data) => { /*金沙棋牌官方平台, do something */ }) .then((previous-promise-data) => { /* do the next thing */ }) .catch( /* handle errors */ )

1
2
3
4
readFile(file1)
    .then((file1-data) => { /* do something */ })
    .then((previous-promise-data) => { /* do the next thing */ })
    .catch( /* handle errors */ )

上面是 Promise 语法。它主要的优点就是可以将队列事件以一种直观的方式链接在一起。虽然这个示例清晰易懂,但是还是用到了回调。Promise 只是让回调显得比较简单和更加直观。

语法


Thunk 函数

这是一种 “传名调用” 的策略,表现的形式就是将参数放入一个临时函数,然后再将这个临时函数传入函数体内。

function asyncFetchDataSource(url){    return function(callback){    fetch(url, callback)    } } const dataSource = asyncFetchDataSource(''); dataSource(function(data){ })

单线程处理程序

JavaScript 是单线程的。当浏览器选项卡执行脚本时,其他所有操作都会停止。这是必然的,因为对页面 DOM 的更改不能并发执行;一个线程
重定向 URL 的同时,另一个线程正要添加子节点,这么做是危险的。

用户不容易察觉,因为处理程序会以组块的形式快速执行。例如,JavaScript 检测到按钮点击,运行计算,并更新 DOM。一旦完成,浏览器就可以自由处理队列中的下一个项目。

(附注: 其它语言比如 PHP 也是单线程,但是通过多线程的服务器比如 Apache 管理。同一 PHP 页面同时发起的两个请求,可以启动两个线程运行,它们是彼此隔离的 PHP 实例。)

现已正式可用

到2017年6月,几乎所有浏览器都可以使用 async 和 await。为了确保你的代码随时可用,则需要使用 Babel 将你的 JavaScript 代码编译为旧浏览器也支持的语法。

如果对更多ES2017内容感兴趣,请访问ES2017特性的完整列表。

1 赞 收藏 评论

金沙棋牌官方平台 1

var gen = async function(){
 var a = await readFIle();
 console.log(b);
 var b = await readFile();
 console.log(b);
}

「文末高能」

Promises

ES2015(ES6) 引入了 Promises。回调函数依然有用,但是 Promises 提供了更清晰的链式异步命令语法,因此可以串联运行(下个章节会讲)。

打算基于 Promise 封装,异步回调函数必须返回一个 Promise 对象。Promise 对象会执行以下两个函数(作为参数传递的)其中之一:

  • resolve:执行成功回调
  • reject:执行失败回调

以下例子,database API 提供了一个 connect() 方法,接收一个回调函数。外部的 asyncDBconnect() 函数立即返回了一个新的 Promise,一旦连接创建成功或失败,resolve()reject() 便会执行:

const db = require('database'); // 连接数据库 function asyncDBconnect(param) { return new Promise((resolve, reject) => { db.connect(param, (err, connection) => { if (err) reject(err); else resolve(connection); }); }); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const db = require('database');
 
// 连接数据库
function asyncDBconnect(param) {
 
  return new Promise((resolve, reject) => {
 
    db.connect(param, (err, connection) => {
      if (err) reject(err);
      else resolve(connection);
    });
 
  });
 
}

Node.js 8.0 以上提供了 util.promisify() 功能,可以把基于回调的函数转换成基于 Promise 的。有两个使用条件:

  1. 传入一个唯一的异步函数
  2. 传入的函数希望是错误优先的(比如:(err, value) => …),error 参数在前,value 随后

举例:

// Node.js: 把 fs.readFile promise 化 const util = require('util'), fs = require('fs'), readFileAsync = util.promisify(fs.readFile); readFileAsync('file.txt');

1
2
3
4
5
6
7
// Node.js: 把 fs.readFile promise 化
const
  util = require('util'),
  fs = require('fs'),
  readFileAsync = util.promisify(fs.readFile);
 
readFileAsync('file.txt');

各种库都会提供自己的 promisify 方法,寥寥几行也可以自己撸一个:

// promisify 只接收一个函数参数 // 传入的函数接收 (err, data) 参数 function promisify(fn) { return function() { return new Promise( (resolve, reject) => fn( ...Array.from(arguments), (err, data) => err ? reject(err) : resolve(data) ) ); } } // 举例 function wait(time, callback) { setTimeout(() => { callback(null, 'done'); }, time); } const asyncWait = promisify(wait); ayscWait(1000);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// promisify 只接收一个函数参数
// 传入的函数接收 (err, data) 参数
function promisify(fn) {
  return function() {
      return new Promise(
        (resolve, reject) => fn(
          ...Array.from(arguments),
        (err, data) => err ? reject(err) : resolve(data)
      )
    );
  }
}
 
// 举例
function wait(time, callback) {
  setTimeout(() => { callback(null, 'done'); }, time);
}
 
const asyncWait = promisify(wait);
 
ayscWait(1000);

ES2017 异步函数现已正式可用

2017/08/22 · JavaScript · ES2017, 异步

原文出处: ERIC WINDMILL   译文出处:葡萄城控件   

ES2017标准已于2017年6月份正式定稿了,并广泛支持最新的特性:异步函数。如果你曾经被异步 JavaScript 的逻辑困扰,这么新函数正是为你设计的。

异步函数或多或少会让你编写一些顺序的 JavaScript 代码,但是却不需要在 callbacks、generators 或 promise 中包含你的逻辑。

如下代码:

function logger() { let data = fetch('') console.log(data) } logger()

1
2
3
4
5
function logger() {
    let data = fetch('http://sampleapi.com/posts')
    console.log(data)
}
logger()

这段代码并未实现你的预期。如果你是在JS中编写的,那么你可能会知道为什么。

下面这段代码,却实现了你的预期。

async function logger() { let data = await fetch('http:sampleapi.com/posts') console.log(data) } logger()

1
2
3
4
5
async function logger() {
    let data = await fetch('http:sampleapi.com/posts')
    console.log(data)
}
logger()

这段代码起作用了,从直观上看,仅仅只是多了 async 和 await 两个词。

var gen = function* (){
 var a = yield readFile();
 console.log(a);
 var b= yield readFile();
 console.log(b);
}

async await

async function createNewDoc() {    let response = await db.post({}); // post a new doc    return await db.get(response.id); // find by id }

根据规范规定一个 asnyc 函数总是要返回一个 Promise,从代码直观上来说,虽然简洁了,但是 async await 并未万能,它有很大的局限性,比如:

  • 因为是顺序执行,假设有三个请求,那么这里并没有很好的利用到异步带来的止损(再包装一个 Promise.all);

  • 如果要捕获异常,需要去包 try catch;

  • 缺少控制流程,比如 progress(进度)pause,resume 等周期性的方法;

  • 没有打断的功能。

前途光明吗?

Promise 减少了回调地狱,但是引入了其他的问题。

教程常常不提,整个 Promise 链条是异步的,一系列的 Promise 函数都得返回自己的 Promise 或者在最终的 .then().catch() 或者 .finally() 方法里面执行回调。

我也承认:Promise 困扰了我很久。语法看起来比回调要复杂,好多地方会出错,调试也成问题。可是,学习基础还是很重要滴。

延伸阅读:

  • MDN Promise documentation
  • JavaScript Promises: an Introduction
  • JavaScript Promises … In Wicked Detail
  • Promises for asynchronous programming

ES6 标准之前的 JavaScript 异步函数

在深入学习 async 和 await 之前,我们需要先理解 Promise。为了领会 Promise,我们需要回到普通回调函数中进一步学习。

Promise 是在 ES6 中引入的,并促使在编写 JavaScript 的异步代码方面,实现了巨大的提升。从此编写回调函数不再那么痛苦。

回调是一个函数,可以将结果传递给函数并在该函数内进行调用,以便作为事件的响应。同时,这也是JS的基础。

function readFile('file.txt', (data) => { // This is inside the callback function console.log(data) }

1
2
3
4
function readFile('file.txt', (data) => {
    // This is inside the callback function
    console.log(data)
}

这个函数只是简单的向文件中记录数据,在文件完成之前进行读取是不可能的。这个过程似乎很简单,但是如果想要按顺序读取并记录五个不同的文件,需要怎么实现呢?

没有 Promise 的时候,为了按顺序执行任务,就需要通过嵌套回调来实现,就像下面的代码:

// This is officially callback hell function combineFiles(file1, file2, file3, printFileCallBack) { let newFileText = '' readFile(string1, (text) => { newFileText += text readFile(string2, (text) => { newFileText += text readFile(string3, (text) => { newFileText += text printFileCallBack(newFileText) } } } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// This is officially callback hell
function combineFiles(file1, file2, file3, printFileCallBack) {
    let newFileText = ''
    readFile(string1, (text) => {
        newFileText += text
        readFile(string2, (text) => {
            newFileText += text
            readFile(string3, (text) => {
                newFileText += text
                printFileCallBack(newFileText)
            }
        }
    }
}

这就很难推断函数下面会发生什么,同时也很难处理各种场景下发生的错误,比如其中某个文件不存在的情况。

promise的写法

function loginOrder(urls){
Const textPromises = urls.map(url => {
Return fetch(url).then(response => response.text());
})
TextPromise.reduce((chain, textPromise) => {
Return chain.then(() => textPromise)
.then(text => console.log(text));
}, Promise.resolve());
}

接下来看一下用async函数来表示上述操作

async function logInOrder(urls) {
for (const url of urls) {
const response = await fetch(url);
console.log(await response.text());
}
}

可以看出来用async表示非常简洁,但是这样会有一个问题所有提取网页都是继发,这样会很浪费时间,继发·就是提取网页是按照顺序进行的,所以我们现在要把它改成同时提取网页,代码如下

async function logInOrder(urls) {
// 并发读取远程URL
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});
// 按次序输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}

因为在上述map函数中,匿名函数是async函数,所以await如果是在async函数中,那么这些await后面的操作都是同步进行的 ,这样就不会耗时了

babel polyfill 支持,在浏览器环境中使用异步解决方案

如果你想使用全的 polyfiil,直接 npm install —save babel-polyfill,然后在 webpack 里进行配置即可。

module.exports = {  entry: ["babel-polyfill", "./app/js"] };

当然由于我目前的开发基于的浏览器都比较高,所以我一般是挑选其中的:

如果你要使用 async await 配置上 即可

通过回调实现异步

单线程产生了一个问题。当 JavaScript 执行一个“缓慢”的处理程序,比如浏览器中的 Ajax 请求或者服务器上的数据库操作时,会发生什么?这些操作可能需要几秒钟 – 甚至几分钟。浏览器在等待响应时会被锁定。在服务器上,Node.js 应用将无法处理其它的用户请求。

解决方案是异步处理。当结果就绪时,一个进程被告知调用另一个函数,而不是等待完成。这称之为回调,它作为参数传递给任何异步函数。例如:

doSomethingAsync(callback1); console.log('finished'); // 当 doSomethingAsync 完成时调用 function callback1(error) { if (!error) console.log('doSomethingAsync complete'); }

1
2
3
4
5
6
7
doSomethingAsync(callback1);
console.log('finished');
 
// 当 doSomethingAsync 完成时调用
function callback1(error) {
  if (!error) console.log('doSomethingAsync complete');
}

doSomethingAsync() 接收回调函数作为参数(只传递该函数的引用,因此开销很小)。doSomethingAsync() 执行多长时间并不重要;我们所知道的是,callback1() 将在未来某个时刻执行。控制台将显示:

finished doSomethingAsync complete

1
2
finished
doSomethingAsync complete

最佳方式:async / await

若干年前,async 函数纳入了 JavaScript 生态系统。就在上个月,async 函数成为了 JavaScript 语言的官方特性,并得到了广泛支持。

async 和 await 是建立在 Promise 和 generator上。本质上,允许我们使用 await 这个关键词在任何函数中的任何我们想要的地方进行暂停。

async function logger() { // pause until fetch returns let data = await fetch('') console.log(data) }

1
2
3
4
5
async function logger() {
    // pause until fetch returns
    let data = await fetch('http://sampleapi.com/posts')
    console.log(data)
}

上面这段代码运行之后,得到了想要的结果。代码从 API 调用中记录了数据。

这种方式的好处就是非常直观。编写代码的方式就是大脑思考的方式,告诉脚本在需要的地方暂停。

另一个好处是,当我们不能使用 promise 时,还可以使用 try 和 catch:

async function logger () { try { let user_id = await fetch('/api/users/username') let posts = await fetch('/api/`${user_id}`') let object = JSON.parse(user.posts.toString()) console.log(posts) } catch (error) { console.error('Error:', error) } }

1
2
3
4
5
6
7
8
9
10
async function logger ()  {
    try {
        let user_id = await fetch('/api/users/username')
        let posts = await fetch('/api/`${user_id}`')
        let object = JSON.parse(user.posts.toString())
        console.log(posts)
    } catch (error) {
        console.error('Error:', error)
    }
}

上面是一个刻意写错的示例,为了证明了一点:在运行过程中,catch 可以捕获任何步骤中发生的错误。至少有三个地方,try 可能会失败,这是在异步代码中的一种最干净的方式来处理错误。

我们还可以使用带有循环和条件的 async 函数:

async function count() { let counter = 1 for (let i = 0; i ) { counter += 1 console.log(counter) await sleep(1000) } }

1
2
3
4
5
6
7
8
async function count() {
    let counter = 1
    for (let i = 0; i ) {
        counter += 1
        console.log(counter)
        await sleep(1000)
    }
}

这是一个很简答的例子,如果运行这段程序,将会看到代码在 sleep 调用时暂停,下一个循环迭代将会在1秒后启动。

接下来用async形式来表示generator函数

总结

相信未来的 JS 编程,只会越来越简单,不要拘泥于语法,语言上的特性,不妨多看一看 “外面的世界”。

近期热文

《敏捷教练 V 形六步法实战:从布朗运动到深度协作》

《从零开始,搭建 AI 音箱 Alexa 语音服务》

《修改订单金额!?0.01 元购买 iPhoneX?| Web谈逻辑漏洞》

**《让你一场 Chat 学会 Git》**

**《接口测试工具 Postman 使用实践》**

**《如何基于 Redis 构建应用程序组件》**

**《深度学习在摄影技术中的应用与发展》**



金沙棋牌官方平台 2

「阅读原文」看交流实录,你想知道的都在这里

回调地狱

通常,回调只由一个异步函数调用。因此,可以使用简洁、匿名的内联函数:

doSomethingAsync(error => { if (!error) console.log('doSomethingAsync complete'); });

1
2
3
doSomethingAsync(error => {
  if (!error) console.log('doSomethingAsync complete');
});

一系列的两个或更多异步调用可以通过嵌套回调函数来连续完成。例如:

async1((err, res) => { if (!err) async2(res, (err, res) => { if (!err) async3(res, (err, res) => { console.log('async1, async2, async3 complete.'); }); }); });

1
2
3
4
5
6
7
async1((err, res) => {
  if (!err) async2(res, (err, res) => {
    if (!err) async3(res, (err, res) => {
      console.log('async1, async2, async3 complete.');
    });
  });
});

不幸的是,这引入了回调地狱 —— 一个臭名昭著的概念,甚至有专门的网页介绍!代码很难读,并且在添加错误处理逻辑时变得更糟。

回调地狱在客户端编码中相对少见。如果你调用 Ajax 请求、更新 DOM 并等待动画完成,可能需要嵌套两到三层,但是通常还算可管理。

操作系统或服务器进程的情况就不同了。一个 Node.js API 可以接收文件上传,更新多个数据库表,写入日志,并在发送响应之前进行下一步的 API 调用。

返回Promise对象

调用async函数会返回一个promise对象,所以才可以调用then方法,then方法中的函数的参数就是async函数返回的值

const aw = async function(age){
 var name = await search(age);
 return name;
}
aw(20).then(name => console.log(name));

传统的 Callback

假定一个 asyncFetchDataSource 函数用于获取远程数据源,可能有 20 秒。

function asyncFetchDataSource(cb){    (… 获取数据 , function(response){    typeof cb === 'function' && cb(response)    })    }

这种形式的 callback 可以适用于简单场景,如果这里有一个更复杂的场景,比如获取完数据源之后,依据 id,获取到某个数据,在这某个数据中再依据 id 来更新某个列表,可以遇见的能看到代码变成了:

asyncFetchDataSource('',function(data_a){    const { id_a } = data_a    asyncFetchDataSource( id_a,function(data_b){    const { id_b } = data_b        asyncFetchDataSource(id, function(data_c){        })    }) })

如果有极端情况出现,这里的 callback 就会变成无极限了。

Promises, Promises

async / await 仍然依赖 Promise 对象,最终依赖回调。你需要理解 Promise 的工作原理,它也并不等同于 Promise.all()Promise.race()。比较容易忽视的是 Promise.all(),这个命令比使用一系列无关的 await 命令更高效。

使用方法


async function getStockByName(name){
 const symbol = await getStockSymbol(name);
 const stockPrice = await getStockPrice(symbol);
 return stockPrice;
}
getStockByName('sportShoe').then(function(result){
 console.log(result);
})

我们可以看出只要我们调用一次getStockByName(),就自动执行,所以最后的result就是stockPrice

主流的异步处理方案

我喜欢用 co,而且社区使用也很广泛,

co(function* () {  var result = yield Promise.resolve(true);  return result; }).then(function (value) {  console.log(value); }, function (err) {  console.error(err.stack); });

异步链式调用

任何返回 Promise 的函数都可以通过 .then() 链式调用。前一个 resolve 的结果会传递给后一个:

asyncDBconnect('') .then(asyncGetSession) // 传递 asyncDBconnect 的结果 .then(asyncGetUser) // 传递 asyncGetSession 的结果 .then(asyncLogAccess) // 传递 asyncGetUser 的结果 .then(result => { // 同步函数 console.log('complete'); // (传递 asyncLogAccess 的结果) return result; // (结果传给下一个 .then()) }) .catch(err => { // 任何一个 reject 触发 console.log('error', err); });

1
2
3
4
5
6
7
8
9
10
11
asyncDBconnect('http://localhost:1234')
  .then(asyncGetSession)      // 传递 asyncDBconnect 的结果
  .then(asyncGetUser)         // 传递 asyncGetSession 的结果
  .then(asyncLogAccess)       // 传递 asyncGetUser 的结果
  .then(result => {           // 同步函数
    console.log('complete');  //   (传递 asyncLogAccess 的结果)
    return result;            //   (结果传给下一个 .then())
  })
  .catch(err => {             // 任何一个 reject 触发
    console.log('error', err);
  });

同步函数也可以执行 .then(),返回的值传递给下一个 .then()(如果有)。

当任何一个前面的 reject 触发时,.catch() 函数会被调用。触发 reject 的函数后面的 .then() 也不再执行。贯穿整个链条可以存在多个 .catch() 方法,从而捕获不同的错误。

ES2018 引入了 .finally() 方法,它不管返回结果如何,都会执行最终逻辑 – 例如,清理操作,关闭数据库连接等等。当前仅有 Chrome 和 Firefox 支持,但是 TC39 技术委员会已经发布了 .finally() 补丁。

function doSomething() { doSomething1() .then(doSomething2) .then(doSomething3) .catch(err => { console.log(err); }) .finally(() => { // 清理操作放这儿! }); }

1
2
3
4
5
6
7
8
9
10
11
function doSomething() {
  doSomething1()
  .then(doSomething2)
  .then(doSomething3)
  .catch(err => {
    console.log(err);
  })
  .finally(() => {
    // 清理操作放这儿!
  });
}

for await of

我们都知道同步遍历器我们可以使用for of来遍历,但是异步的遍历器,我们使用for await of来遍历

async function f() {
for await (const x of createAsyncIterable(['a', 'b'])) {
 console.log(x);
  }
}
// a
// b

Event loop

虽然异步与 event loop 没有太直接的关系,准确的来讲 event loop 只是实现异步的一种机制。(了解为主)

还是以上面咖啡馆为例子,假定场景还是 100 人,这 100 人除了下单是与咖啡本身有关联之外,其余的时间,比如看书,玩游戏的等可以视为自己的执行逻辑。

如果用 event loop 来给它做一个简单的画像,那么它就像:在与咖啡店店员沟通下单视为主执行栈,咖啡的制作可以视为一个异步任务,添加到一个任务队列里,一直等带 100 个人都下单完成,然后开始读取任务队列中的异步任务,事件名就是下单凭证,如果有对应的 handler,那么就执行叫对应的客人来领取咖啡。

这个过程,是循环不断的。假设没有客人来下单的时候,也就是店员处于空闲时间(可能自己去搞点别的)。

丑陋的 try/catch

如果执行失败的 await 没有包裹 try / catchasync 函数将静默退出。如果有一长串异步 await 命令,需要多个 try / catch 包裹。

替代方案是使用高阶函数来捕捉错误,不再需要 try / catch 了(感谢@wesbos的建议):

async function connect() { const connection = await asyncDBconnect(''), session = await asyncGetSession(connection), user = await asyncGetUser(session), log = await asyncLogAccess(user); return true; } // 使用高阶函数捕获错误 function catchErrors(fn) { return function (...args) { return fn(...args).catch(err => { console.log('ERROR', err); }); } } (async () => { await catchErrors(connect)(); })();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function connect() {
 
  const
    connection = await asyncDBconnect('http://localhost:1234'),
    session = await asyncGetSession(connection),
    user = await asyncGetUser(session),
    log = await asyncLogAccess(user);
 
  return true;
}
 
// 使用高阶函数捕获错误
function catchErrors(fn) {
  return function (...args) {
    return fn(...args).catch(err => {
      console.log('ERROR', err);
    });
  }
}
 
(async () => {
  await catchErrors(connect)();
})();

当应用必须返回区别于其它的错误时,这种作法就不太实用了。

尽管有一些缺陷,async/await 还是 JavaScript 非常有用的补充。更多资源:

  • MDN async 和 await
  • 异步函数 – 提高 Promise 的易用性
  • TC39 异步函数规范
  • 用异步函数简化异步编码

promise对象的状态变化

只有内部的所有异步过程结束后,才会调用then方法,或者抛出错误,或者遇到return语句

Promise

Promise 正是想来处理这样的异步编程,如果我们用 Promise 该如何处理一段 Ajax?

function fetch(){  return new Promise(function(resolve,reject){    $.ajax({      url: 'xxx',      success:function(data){        resolve(data)      },      error:function(error){        reject(error)      }    })  }) } fetch().then(function(data){ }).catch(function(error){})

Promise 声明周期:

  • 进行中(pending)

  • 已经完成(fulfilled)

  • 拒绝(rejected)

如同上面 Ajax 的例子,我们可以很好的包装一个函数,让 fetch 函数返回一个 Promise 对象。

在 Promise 构造函数里,可以传入一个 callback,并且在这里完成主体逻辑的编写。唯一需要注意的是:Promise 对象只能通过 resolve 和 reject 函数来返回,在外部使用 then 或 catch 来获取。

如果你直接抛出一个错误(throw new Error(‘error’)),catch 也是可以正确的捕获到的。

JavaScript 之旅

异步编程是 JavaScript 无法避免的挑战。回调在大多数应用中是必不可少的,但是容易陷入深度嵌套的函数中。

Promise 抽象了回调,但是有许多句法陷阱。转换已有函数可能是一件苦差事,·then() 链式调用看起来很凌乱。

很幸运,async/await 表达清晰。代码看起来是同步的,但是又不独占单个处理线程。它将改变你书写 JavaScript 的方式,甚至让你更赏识 Promise – 如果没接触过的话。

1 赞 收藏 评论

金沙棋牌官方平台 3

错误处理

我建议大家把所有的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);
}
}

Node.js 环境中使用异步解决方案

由于本人的 node 使用的 LTS 已经是 8.9.3 版本了,所以大部分情况下已经不再使用 babel 去进行转换,而是直接使用 co 这样的库。

当然 co 也不是万能,一定要根据业务场景,与其他异步处理的方式,配合中使用。

现代 JS 流程控制:从回调函数到 Promises 再到 Async/Await

2018/09/03 · JavaScript · Promises

原文出处: Craig Buckler   译文出处:OFED   

JavaScript 通常被认为是异步的。这意味着什么?对开发有什么影响呢?近年来,它又发生了怎样的变化?

看看以下代码:

result1 = doSomething1(); result2 = doSomething2(result1);

1
2
result1 = doSomething1();
result2 = doSomething2(result1);

大多数编程语言同步执行每行代码。第一行执行完毕返回一个结果。无论第一行代码执行多久,只有执行完成第二行代码才会执行。

哪些改进呢?


  • 内置执行器
    在我的另一篇博客中说出generator函数执行,需要为他写自动执行器,例如基于thunk函数的自动执行器,还有基于promise的自动执行器,还有就是co模块,但是async函数只要调用它就自己执行
  • 更好的语义
  • 更广泛的使用
    co模块的yield语句后面必须是thunk函数或者是promise对象,但是await函数后面可以是原始类型的值
  • 返回的是promise对象
    可以使用then等

Generator

Generator 可以辅助我们完成很多复杂的任务,而这些基础知识,又与 iterator 息息相关。

举一个很简单的例子,相信有很多朋友,应该使用过 co 这个异步编程的库,它就是用 Generator 来实现,当然它的设计会比例子要复杂的多,我们先来看一个 co 简单的用法:

import co from 'co' co(function* () {  var result = yield Promise.resolve(true);  return result; }).then(function (value) {  console.log(value); }, function (err) {  console.error(err.stack); });

相应的,我们来实现一个简化的版本:

function co(task){  let _task = task()  let resl = _task.next();  while(!resl.done){    console.log(resl);    resl = _task.next(resl.value);  } } function sayName(){  return {    name: 'icepy'  } } function assign *(f){  console.log(f)  let g = yield sayName()  return Object.assign(g,{age:f}); } co(function *(){  let info = yield *assign(18)  console.log(info) })

虽然,这个例子中,还不能很好的看出来 “异步” 的场景,但是它很好的描述了 Generator 的使用方式。

从最开始的定义中,已经和大家说明了,Generator 最终返回的依然是一个迭代器对象,有了这个迭代器对象,当你在处理某些场景时,你可以通过 yield 来控制,流程的走向。

通过 co 函数,我们可以看出,先来执行 next 方法,然后通过一个 while 循环,来判断 done 是否为 true,如果为 true 则代表整个迭代过程的结束,于是,这里就可以退出循环了。在 Generator 中的返回值,可以通过给 next 方法传递参数的方式来实现,也就是遇上第一个 yield 的返回值。

有逻辑,自然会存在错误,在 Generator 捕获错误的时机与执行 throw 方法的顺序有关系,一个小例子:

let hu = function *(){  let g = yield 1;  try {    let j = yield 2;  } catch(e){    console.log(e)  }  return 34 } let _it = hu(); console.log(_it.next()) console.log(_it.next()) console.log(_it.throw(new Error('hu error')))

当我能捕获到错误的时机是允许完第二次的 yield,这个时候就可以 try 了。

使用 Promise.all() 处理多个异步操作

Promise .then() 方法用于相继执行的异步函数。如果不关心顺序 – 比如,初始化不相关的组件 – 所有异步函数同时启动,直到最慢的函数执行 resolve,整个流程结束。

Promise.all() 适用于这种场景,它接收一个函数数组并且返回另一个 Promise。举例:

Promise.all([ async1, async2, async3 ]) .then(values => { // 返回值的数组 console.log(values); // (与函数数组顺序一致) return values; }) .catch(err => { // 任一 reject 被触发 console.log('error', err); });

1
2
3
4
5
6
7
8
Promise.all([ async1, async2, async3 ])
  .then(values => {           // 返回值的数组
    console.log(values);      // (与函数数组顺序一致)
    return values;
  })
  .catch(err => {             // 任一 reject 被触发
    console.log('error', err);
  });

任意一个异步函数 rejectPromise.all() 会立即结束。

异步遍历

我们都知道同步遍历器是部署在对象的Symbol.iterator的属性上的,但是异步遍历器是部署在对象的Symbol.asyncIterator属性上的
以下代码是一个异步遍历器的例子

const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable.Symbol.asyncIterator();
asyncIterator
.next()
.then(iterResult1 => {
  console.log(iterResult1); // { value: 'a', done: false }
  return asyncIterator.next();
})
.then(iterResult2 => {
 console.log(iterResult2); // { value: 'b', done: false }
 return asyncIterator.next();
})
.then(iterResult3 => {
  console.log(iterResult3); // { value: undefined, done: true }
});

从上面可以看出异步遍历器调用next方法返回一个promise,然后promise的状态变为resolve,然后调用then函数,执行then函数内的回调函数
由于调用next方法返回的是一个promise对象,所以说可以放在await命令后面,代码如下

async function f() {
 const asyncIterable = createAsyncIterable(['a', 'b']);
 const asyncIterator = asyncIterableSymbol.asyncIterator;
 console.log(await asyncIterator.next());
  // { value: 'a', done: false }
  console.log(await asyncIterator.next());
 // { value: 'b', done: false }
 console.log(await asyncIterator.next());
 // { value: undefined, done: true }
}

上面的代码接近于同步执行,但是我们想要所有的await基本上同时进行,可以有一下俩种表示

const asyncGenObj = createAsyncIterable(['a', 'b']);
const [{value: v1}, {value: v2}] = await Promise.all([
 asyncGenObj.next(), asyncGenObj.next()
]);
console.log(v1, v2); // a b
//第二种
async function runner() {
  const writer = openFile('someFile.txt');
 writer.next('hello');
 writer.next('world');
 await writer.return();
}
runner();

为什么要异步

“当我们在星巴克买咖啡时,假设有 100 个人在排队,也许咖啡的下单只要 10 秒,但是咖啡的制作到客人领取咖啡要 1000 秒。如果在同步的场景下,第一个客人下单到领取完咖啡要 1010 秒才能轮到下一个客人,这在效率(某些场景)上来说会比较低下。

如果我们异步处理这个流程,客人下单 10 秒拿到凭证,客人就可以去做别的事情,并且 10 秒后下一个客人可以继续下单,并不阻碍流程。反而可以通过凭证,让客人拿到自己的咖啡,也许时间上并不是第一个下单的客人先拿到。

在网页的世界里也是同样的道理,不妨我们看看在执行 JS 代码的主线程里,如果遇到了 AJAX 请求,用户事件等,如果不采用异步的方案,你会一直等待,等待第一个耗时的处理完成才能接上下一个 JS 代码的执行,于是界面就卡住了。

也许有人会想,既然大家都说现在网页上性能损耗最大的属于 DOM 节点的操作,把这些搞成异步,行不行?其实这会带来一个不确定性问题:既 “成功” 的状态到底谁先来的问题。

可以想象一下,如果我们在操作 DOM,既给节点添加内容,也给节点删除,那么到底以谁为基准呢?考虑到复杂性,也就可见一斑了。

使用 Promise.race() 处理多个异步操作

Promise.race()Promise.all() 极其相似,不同之处在于,当首个 Promise resolve 或者 reject 时,它将会 resolve 或者 reject。仅有最快的异步函数会被执行:

Promise.race([ async1, async2, async3 ]) .then(value => { // 单一值 console.log(value); return value; }) .catch(err => { // 任一 reject 被触发 console.log('error', err); });

1
2
3
4
5
6
7
8
Promise.race([ async1, async2, async3 ])
  .then(value => {            // 单一值
    console.log(value);
    return value;
  })
  .catch(err => {             // 任一 reject 被触发
    console.log('error', err);
  });

从以上可以看出async函数就是在generator函数上改进的,就是把*改为async,然后把yield改为await,但是他在generator函数上有一些改进

金沙棋牌官方平台 4

Async/Await

Promise 看起来有点复杂,所以 ES2017 引进了 asyncawait。虽然只是语法糖,却使 Promise 更加方便,并且可以避免 .then() 链式调用的问题。看下面使用 Promise 的例子:

function connect() { return new Promise((resolve, reject) => { asyncDBconnect('') .then(asyncGetSession) .then(asyncGetUser) .then(asyncLogAccess) .then(result => resolve(result)) .catch(err => reject(err)) }); } // 运行 connect 方法 (自执行方法) (() => { connect(); .then(result => console.log(result)) .catch(err => console.log(err)) })();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function connect() {
 
  return new Promise((resolve, reject) => {
 
    asyncDBconnect('http://localhost:1234')
      .then(asyncGetSession)
      .then(asyncGetUser)
      .then(asyncLogAccess)
      .then(result => resolve(result))
      .catch(err => reject(err))
 
  });
}
 
// 运行 connect 方法 (自执行方法)
(() => {
  connect();
    .then(result => console.log(result))
    .catch(err => console.log(err))
})();

使用 async / await 重写上面的代码:

  1. 外部方法用 async 声明
  2. 基于 Promise 的异步方法用 await 声明,可以确保下一个命令执行前,它已执行完成

async function connect() { try { const connection = await asyncDBconnect(''), session = await asyncGetSession(connection), user = await asyncGetUser(session), log = await asyncLogAccess(user); return log; } catch (e) { console.log('error', err); return null; } } // 运行 connect 方法 (自执行异步函数) (async () => { await connect(); })();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function connect() {
 
  try {
    const
      connection = await asyncDBconnect('http://localhost:1234'),
      session = await asyncGetSession(connection),
      user = await asyncGetUser(session),
      log = await asyncLogAccess(user);
 
    return log;
  }
  catch (e) {
    console.log('error', err);
    return null;
  }
 
}
 
// 运行 connect 方法 (自执行异步函数)
(async () => { await connect(); })();

await 使每个异步调用看起来像是同步的,同时不耽误 JavaScript 的单线程处理。此外,async 函数总是返回一个 Promise 对象,因此它可以被其他 async 函数调用。

async / await 可能不会让代码变少,但是有很多优点:

  1. 语法更清晰。括号越来越少,出错的可能性也越来越小。
  2. 调试更容易。可以在任何 await 声明处设置断点。
  3. 错误处理尚佳。try / catch 可以与同步代码使用相同的处理方式。
  4. 支持良好。所有浏览器(除了 IE 和 Opera Mini )和 Node7.6+ 均已实现。

如是说,没有完美的…

顺序执行完一系列操作

本文来自作者 icepy 在 GitChat 上分享 「深入浅出 JS 异步处理技术方案」,「阅读原文」查看交流实录。

首先看一下下列代码

编辑 | 哈比

注意的内容

let foo = await getFoo();
let bar = await getBar();

以上函数是独立的过程,被写成继发关系,就是顺序进行,这样会导致很费时,怎样会写成同时进行
有一下俩种写法

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

promise.all()方法就是将多个promise实例包装成一个
await命令必须写在async函数中写在别的函数中会出错

async函数的实现的原理

其实就是把generator函数写到一个具有自动执行代码的函数,然后在返回这个函数,和基于thunk函数的自动执行器基本一致,就不仔细分析async函数的源码了

本文由金沙棋牌发布于金沙棋牌官方平台,转载请注明出处:异步处理技术方案,异步函数现已正式可用

关键词:

上一篇:没有了

下一篇:没有了