2018-11-07 18:28:20 zunguitiancheng 阅读数 582

 

https://www.cnblogs.com/yaopengfei/archive/2018/07/02/9249390.html

 

一. 同步VS异步

1.   同步 VS 异步 VS 多线程

同步方法:调用时需要等待返回结果,才可以继续往下执行业务

异步方法:调用时无须等待返回结果,可以继续往下执行业务

开启新线程:在主线程之外开启一个新的线程去执行业务

同步方法和异步方法的本质区别: 调用时是否需要等待返回结果才能继续执行业务

2. 常见的异步方法(都以Async结尾)

  ① HttpClient类:PostAsync、PutAsync、GetAsync、DeleteAsync

  ② EF中DbContext类:SaveChangesAsync

  ③ 文件相关中的:WriteLineAsync

3. 引入异步方法的背景

  比如我在后台要向另一台服务器中获取中的2个接口获取信息,然后将两个接口的信息拼接起来,一起输出,接口1耗时3s,接口2耗时5s,

① 传统的同步方式:

  需要的时间大约为:3s + 5s =8s, 如下面 【案例1】

先分享一个同步请求接口的封装方法,下同。

复制代码

 1   public class HttpService
 2     {
 3         /// <summary>
 4         /// 后台跨域请求发送代码
 5         /// </summary> 
 6         /// <param name="url">eg:http://ac.guojin.org/jeesite/regist/saveAppAgentAccount </param>
 7         ///<param name="postData"></param>
 8         ///  参数格式(手拼Json) string postData = "{\"name\":\"" + vip.comName + "\",\"shortName\":\"" + vip.shortName + + "\"}";             
 9         /// <returns></returns>
10         public static string PostData(string postData, string url)
11         {
12             HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);//后台请求页面
13             Encoding encoding = Encoding.GetEncoding("utf-8");//注意页面的编码,否则会出现乱码
14             byte[] requestBytes = encoding.GetBytes(postData);
15             req.Method = "POST";
16             req.ContentType = "application/json";
17             req.ContentLength = requestBytes.Length;
18             Stream requestStream = req.GetRequestStream();
19             requestStream.Write(requestBytes, 0, requestBytes.Length);
20             requestStream.Close();
21             HttpWebResponse res = (HttpWebResponse)req.GetResponse();
22             StreamReader sr = new StreamReader(res.GetResponseStream(), System.Text.Encoding.GetEncoding("utf-8"));
23             string backstr = sr.ReadToEnd();//可以读取到从页面返回的结果,以数据流的形式。
24             sr.Close();
25             res.Close();
26 
27             return backstr;
28         }

复制代码

然后在分享服务上的耗时操作,下同。

复制代码

 1  /// <summary>
 2         /// 耗时方法  耗时3s
 3         /// </summary>
 4         /// <returns></returns>
 5         public ActionResult GetMsg1()
 6         {
 7             Thread.Sleep(3000);
 8             return Content("GetMsg1");
 9 
10         }
11 
12         /// <summary>
13         /// 耗时方法  耗时5s
14         /// </summary>
15         /// <returns></returns>
16         public ActionResult GetMsg2()
17         {
18             Thread.Sleep(5000);
19             return Content("GetMsg2");
20 
21         }

复制代码

下面是案例1代码

复制代码

 1        #region 案例1(传统同步方式 耗时8s左右)
 2             {
 3                 Stopwatch watch = Stopwatch.StartNew();
 4                 Console.WriteLine("开始执行");
 5 
 6                 string t1 = HttpService.PostData("", "http://localhost:2788/Home/GetMsg1");
 7                 string t2 = HttpService.PostData("", "http://localhost:2788/Home/GetMsg2");
 8 
 9                 Console.WriteLine("我是主业务");
10                 Console.WriteLine($"{t1},{t2}");
11                 watch.Stop();
12                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
13             }
14             #endregion

复制代码

② 开启新线程分别执行两个耗时操作

  需要的时间大约为:Max(3s,5s) = 5s ,如下面【案例2】

复制代码

 1         #region 案例2(开启新线程分别执行两个耗时操作 耗时5s左右)
 2             {
 3                 Stopwatch watch = Stopwatch.StartNew();
 4                 Console.WriteLine("开始执行");
 5 
 6                 var task1 = Task.Run(() =>
 7                 {
 8                     return HttpService.PostData("", "http://localhost:2788/Home/GetMsg1");
 9                 });
10 
11                 var task2 = Task.Run(() =>
12                 {
13                     return HttpService.PostData("", "http://localhost:2788/Home/GetMsg2");
14                 });
15 
16                 Console.WriteLine("我是主业务");
17                 //主线程进行等待
18                 Task.WaitAll(task1, task2);
19                 Console.WriteLine($"{task1.Result},{task2.Result}");
20                 watch.Stop();
21                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
22             }
23             #endregion

复制代码

  既然②方式可以解决同步方法串行耗时间的问题,但这种方式存在一个弊端,一个业务中存在多个线程,且需要对线程进行管理,相对麻烦,从而引出了异步方法。

这里的异步方法 我 特指:系统类库自带的以async结尾的异步方法。

③ 使用系统类库自带的异步方法

  需要的时间大约为:Max(3s,5s) = 5s ,如下面【案例3】

复制代码

 1       #region 案例3(使用系统类库自带的异步方法 耗时5s左右)
 2             {
 3                 Stopwatch watch = Stopwatch.StartNew();
 4                 HttpClient http = new HttpClient();
 5                 var httpContent = new StringContent("", Encoding.UTF8, "application/json");
 6                 Console.WriteLine("开始执行");
 7                 //执行业务
 8                 var r1 = http.PostAsync("http://localhost:2788/Home/GetMsg1", httpContent);
 9                 var r2 = http.PostAsync("http://localhost:2788/Home/GetMsg2", httpContent);
10                 Console.WriteLine("我是主业务");
11 
12                 //通过异步方法的结果.Result可以是异步方法执行完的结果
13                 Console.WriteLine(r1.Result.Content.ReadAsStringAsync().Result);
14                 Console.WriteLine(r2.Result.Content.ReadAsStringAsync().Result);
15 
16                 watch.Stop();
17                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
18             }
19             #endregion

复制代码

PS:通过 .Result 来获取异步方法执行完后的结果。

二. 利用async和await封装异步方法

1. 首先要声明几点:

  ① async和await关键字是C# 5.0时代引入的,它是一种异步编程模型

  ② 它们本身并不创建新线程,但我可以在自行封装的async中利用Task.Run开启新线程

  ③ 利用async关键字封装的方法中如果写全部都是一些串行业务, 且不用await关键字,那么即使使用async封装,也并没有什么卵用,并起不了异步方法的作用。

   需要的时间大约为:3s + 5s =8s, 如下面 【案例4】,并且封装的方法编译器会提示:“缺少关键字await,将以同步的方式调用,请使用await运算符等待非阻止API或Task.Run的形式”(PS:非阻止API指系统类库自带的以Async结尾的异步方法)

 View Code

复制代码

 1            #region 案例4(async关键字封装的方法中如果写全部都是一些串行业务 耗时8s左右)
 2             {
 3                 Stopwatch watch = Stopwatch.StartNew();
 4 
 5                 Console.WriteLine("开始执行");
 6 
 7                 Task<string> t1 = NewMethod5Async();
 8                 Task<string> t2 = NewMethod6Async();
 9 
10                 Console.WriteLine("我是主业务");
11                 Console.WriteLine($"{t1.Result},{t2.Result}");
12                 watch.Stop();
13                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
14             }
15             #endregion

复制代码

  观点结论1:从上面③中可以得出一个结论,async中必须要有await运算符才能起到异步方法的作用,且await 运算符只能加在 系统类库默认提供的异步方法或者新线程(如:Task.Run)前面。

   如:下面【案例5】 和 【案例6】需要的时间大约为:Max(3s,5s) = 5s

 View Code

复制代码

 1        #region 案例5(将系统类库提供的异步方法利用async封装起来 耗时5s左右)
 2             //并且先输出“我是主业务”,证明t1和t2是并行执行的,且不阻碍主业务
 3             {
 4                 Stopwatch watch = Stopwatch.StartNew();
 5 
 6                 Console.WriteLine("开始执行");
 7                 Task<string> t1 = NewMethod1Async();
 8                 Task<string> t2 = NewMethod2Async();
 9 
10                 Console.WriteLine("我是主业务");
11                 Console.WriteLine($"{t1.Result},{t2.Result}");
12                 watch.Stop();
13                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
14             }
15             #endregion

复制代码

复制代码

 1        #region 案例6(将新线程利用async封装起来 耗时5s左右)
 2             //并且先输出“我是主业务”,证明t1和t2是并行执行的,且不阻碍主业务
 3             {
 4                 Stopwatch watch = Stopwatch.StartNew();
 5 
 6                 Console.WriteLine("开始执行");
 7                 Task<string> t1 = NewMethod3Async();
 8                 Task<string> t2 = NewMethod4Async();
 9 
10                 Console.WriteLine("我是主业务");
11                 Console.WriteLine($"{t1.Result},{t2.Result}");
12                 watch.Stop();
13                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
14             }
15             #endregion

复制代码

2. 几个规则和约定

  ① async封装的方法中,可以有多个await,这里的await代表等待该行代码执行完毕。

  ② 我们通常自己封装的方法也要以Async结尾,方便识别

  ③ 异步返回类型主要有三种:Task<T> 、Task、Void

3. 测试得出其他几个结论

① 如果async封装的异步方法里既有同步业务又有异步业务(开启新线程或者系统类库提供异步方法),那么同步方法那部分的时间在调用的时候是会阻塞主线程的,即主线程要等待这部分同步业务执行完才能往下执行。

  如【案例7】 耗时:同步操作之和 2s+2s + Max(3s,5s)=9s;

 View Code

复制代码

 1       #region 案例7(既有普通的耗时操作,也有系统本身的异步方法,耗时9s左右)
 2             //且大约4s后才能输出 “我是主业务”,证明同步操作Thread.Sleep(2000);  阻塞主线程
 3             {
 4                 Stopwatch watch = Stopwatch.StartNew();
 5 
 6                 Console.WriteLine("开始执行");
 7                 Task<string> t1 = NewMethod7Async();
 8                 Task<string> t2 = NewMethod8Async();
 9 
10                 Console.WriteLine("我是主业务");
11                 Console.WriteLine($"{t1.Result},{t2.Result}");
12                 watch.Stop();
13                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
14             }
15             #endregion

复制代码

  

  证明:async封装的异步方法里的同步业务的时间会阻塞主线程,再次证明 await只能加在 非阻止api和开启新线程的前面

② 如果封装的异步方法中存在等待的问题,而且不能阻塞主线程(不能用Thread.Sleep) , 这个时候可以用Task.Delay,并在前面加await关键字

  如【案例8】 耗时:Max(2+3 , 5+2)=7s

 View Code

复制代码

 1         #region 案例8(利用Task.Delay执行异步方法的等待操作)
 2             //结果是7s,且马上输出“我是主业务”,说明Task.Delay(),不阻塞主线程。
 3             {
 4                 Stopwatch watch = Stopwatch.StartNew();
 5                 Console.WriteLine("开始执行");
 6                 Task<string> t1 = NewMethod11Async();
 7                 Task<string> t2 = NewMethod12Async();
 8 
 9                 Console.WriteLine("我是主业务");
10                 Console.WriteLine($"{t1.Result},{t2.Result}");
11                 watch.Stop();
12                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
13             }
14             #endregion

复制代码

三. 异步方法返回类型

1. Task<T>, 处理含有返回值的异步方法,通过 .Result 等待异步方法执行完,且获取到返回值。

2. Task:调用方法不需要从异步方法中取返回值,但是希望检查异步方法的状态,那么可以选择可以返回 Task 类型的对象。不过,就算异步方法中包含 return 语句,也不会返回任何东西。

  如【案例9】

 View Code

复制代码

 1        #region 案例9(返回值为Task的异步方法)
 2             //结果是5s,说明异步方法和主线程的同步方法 在并行执行
 3             {
 4                 Stopwatch watch = Stopwatch.StartNew();
 5 
 6                 Console.WriteLine("开始执行");
 7                 Task t = NewMethod9Async();
 8 
 9                 Console.WriteLine($"{nameof(t.Status)}: {t.Status}");   //任务状态
10                 Console.WriteLine($"{nameof(t.IsCompleted)}: {t.IsCompleted}");     //任务完成状态标识
11                 Console.WriteLine($"{nameof(t.IsFaulted)}: {t.IsFaulted}");     //任务是否有未处理的异常标识
12 
13                 //执行其他耗时操作,与此同时NewMethod9Async也在工作
14                 Thread.Sleep(5000);
15      
16                 Console.WriteLine("我是主业务");
17 
18                 t.Wait();
19 
20                 Console.WriteLine($"{nameof(t.Status)}: {t.Status}");   //任务状态
21                 Console.WriteLine($"{nameof(t.IsCompleted)}: {t.IsCompleted}");     //任务完成状态标识
22                 Console.WriteLine($"{nameof(t.IsFaulted)}: {t.IsFaulted}");     //任务是否有未处理的异常标识
23 
24                 Console.WriteLine($"所有业务执行完成了");
25                 watch.Stop();
26                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
27             }
28             #endregion

复制代码

  PS:对于Task返回值的异步方法,可以调用Wait(),等 待该异步方法执行完,他和await不同,await必须出现在async关键字封装的方法中。

3. void:调用异步执行方法,不需要做任何交互

  如【案例10】

 View Code

复制代码

 1         #region 案例10(返回值为Void的异步方法)
 2             //结果是5s,说明异步方法和主线程的同步方法 在并行执行
 3             {
 4                 Stopwatch watch = Stopwatch.StartNew();
 5 
 6                 Console.WriteLine("开始执行");
 7                 NewMethod10Async();
 8 
 9                 //执行其他耗时操作,与此同时NewMethod9Async也在工作
10                 Thread.Sleep(5000);
11 
12                 Console.WriteLine("我是主业务");
13 
14 
15                 Console.WriteLine($"所有业务执行完成了");
16                 watch.Stop();
17                 Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}");
18             }
19             #endregion

复制代码

四. 几个结论

1. 异步方法到底开不开起新线程?

  异步和等待关键字不会导致其他线程创建。 因为异步方法本身并不会运行的线程,异步方法不需要多线程。 只有 + 当方法处于活动状态,则方法在当前同步上下文中运行并使用在线程的时间。 可以使用 Task.Run 移动 CPU 工作移到后台线程,但是,后台线程不利于等待结果变得可用处理。(来自MSDN原话)

2. async和await是一种异步编程模型,它本身并不能开启新线程,多用于将一些非阻止API或者开启新线程的操作封装起来,使其调用的时候像同步方法一样使用。

下面补充博客园dudu的解释,方便大家理解。

 

五. 参考资料

   1. 反骨仔:http://www.cnblogs.com/liqingwen/p/5831951.html

        http://www.cnblogs.com/liqingwen/p/5844095.html

  2. MSDN:https://msdn.microsoft.com/library/hh191443(vs.110).aspx

 

PS:如果你想了解多线程的其他知识,请移步:那些年我们一起追逐的多线程(Thread、ThreadPool、委托异步调用、Task/TaskFactory、Parallerl、async和await)

2018-11-28 21:33:43 qq_42335056 阅读数 0

 先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。 写一个async 函数

async function timeout() {
  return 'hello world';
}

   语法很简单,就是在函数前面加上async 关键字,来表示它是异步的,那怎么调用呢?async 函数也是函数,平时我们怎么使用函数就怎么使用它,直接加括号调用就可以了,为了表示它没有阻塞它后面代码的执行,我们在async 函数调用之后加一句console.log;

async function timeout() {
    return 'hello world'
}
timeout();
console.log('虽然在后面,但是我先执行');

  打开浏览器控制台,我们看到了

  async 函数 timeout  调用了,但是没有任何输出,它不是应该返回 'hello world',  先不要着急, 看一看timeout()执行返回了什么? 把上面的 timeout() 语句改为console.log(timeout())

async function timeout() {
    return 'hello world'
}
console.log(timeout());
console.log('虽然在后面,但是我先执行');

  继续看控制台

  原来async 函数返回的是一个promise 对象,如果要获取到promise 返回值,我们应该用then 方法, 继续修改代码

复制代码

async function timeout() {
    return 'hello world'
}
timeout().then(result => {
    console.log(result);
})
console.log('虽然在后面,但是我先执行');

复制代码

  看控制台

  我们获取到了"hello world',  同时timeout 的执行也没有阻塞后面代码的执行,和 我们刚才说的一致。

  这时,你可能注意到控制台中的Promise 有一个resolved,这是async 函数内部的实现原理。如果async 函数中有返回一个值 ,当调用该函数时,内部会调用Promise.solve() 方法把它转化成一个promise 对象作为返回,但如果timeout 函数内部抛出错误呢? 那么就会调用Promise.reject() 返回一个promise 对象, 这时修改一下timeout 函数

复制代码

async function timeout(flag) {
    if (flag) {
        return 'hello world'
    } else {
        throw 'my god, failure'
    }
}
console.log(timeout(true))  // 调用Promise.resolve() 返回promise 对象。
console.log(timeout(false)); // 调用Promise.reject() 返回promise 对象。

复制代码

  控制台如下:

  如果函数内部抛出错误, promise 对象有一个catch 方法进行捕获。

timeout(false).catch(err => {
    console.log(err)
})

  async 关键字差不多了,我们再来考虑await 关键字,await是等待的意思,那么它等待什么呢,它后面跟着什么呢?其实它后面可以放任何表达式,不过我们更多的是放一个返回promise 对象的表达式。注意await 关键字只能放到async 函数里面

  现在写一个函数,让它返回promise 对象,该函数的作用是2s 之后让数值乘以2

复制代码

// 2s 之后返回双倍的值
function doubleAfter2seconds(num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2 * num)
        }, 2000);
    } )
}

复制代码

  现在再写一个async 函数,从而可以使用await 关键字, await 后面放置的就是返回promise对象的一个表达式,所以它后面可以写上 doubleAfter2seconds 函数的调用

async function testResult() {
    let result = await doubleAfter2seconds(30);
    console.log(result);
}

  现在调用testResult 函数

testResult();

  打开控制台,2s 之后,输出了60. 

  现在我们看看代码的执行过程,调用testResult 函数,它里面遇到了await, await 表示等一下,代码就暂停到这里,不再向下执行了,它等什么呢?等后面的promise对象执行完毕,然后拿到promise resolve 的值并进行返回,返回值拿到之后,它继续向下执行。具体到 我们的代码, 遇到await 之后,代码就暂停执行了, 等待doubleAfter2seconds(30) 执行完毕,doubleAfter2seconds(30) 返回的promise 开始执行,2秒 之后,promise resolve 了, 并返回了值为60, 这时await 才拿到返回值60, 然后赋值给result, 暂停结束,代码才开始继续执行,执行 console.log语句。

  就这一个函数,我们可能看不出async/await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?

复制代码

async function testResult() {
    let first = await doubleAfter2seconds(30);
    let second = await doubleAfter2seconds(50);
    let third = await doubleAfter2seconds(30);
    console.log(first + second + third);
}

复制代码

  6秒后,控制台输出220, 我们可以看到,写异步代码就像写同步代码一样了,再也没有回调地域了。

  再写一个真实的例子,我原来做过一个小功能,话费充值,当用户输入电话号码后,先查找这个电话号码所在的省和市,然后再根据省和市,找到可能充值的面值,进行展示。

为了模拟一下后端接口,我们新建一个node 项目。 新建一个文件夹 async, 然后npm init -y 新建package.json文件,npm install express --save 安装后端依赖,再新建server.js 文件作为服务端代码, public文件夹作为静态文件的放置位置, 在public 文件夹里面放index.html 文件, 整个目录如下

 

  server.js 文件如下,建立最简单的web 服务器

复制代码

const express = require('express');
const app = express();// express.static 提供静态文件,就是html, css, js 文件
app.use(express.static('public'));

app.listen(3000, () => {
    console.log('server start');
})

复制代码

  再写index.html 文件,我在这里用了vue构建页面,用axios 发送ajax请求, 为了简单,用cdn 引入它们。 html部分很简单,一个输入框,让用户输入手机号,一个充值金额的展示区域, js部分,按照vue 的要求搭建了模版

复制代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Async/await</title>
    <!-- CDN 引入vue 和 axios -->
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
    <div id="app">

        <!-- 输入框区域 -->
        <div style="height:50px">
            <input type="text" placeholder="请输入电话号码" v-model="phoneNum">
            <button @click="getFaceResult">确定</button>
        </div>

        <!-- 充值面值 显示区域 -->
        <div>
            充值面值:
            <span v-for="item in faceList" :key='item'>
                {{item}}
            </span>
        </div>
    </div>

    <!-- js 代码区域 -->
    <script>
        new Vue({
            el: '#app',
            data: {
                phoneNum: '12345',
                faceList: ["20元", "30元", "50元"]
            },
            methods: {
                getFaceResult() {

                }
            }
        })
    </script>
</body>
</html>

复制代码

  为了得到用户输入的手机号,给input 输入框添加v-model指令,绑定phoneNum变量。展示区域则是 绑定到faceList 数组,v-for 指令进行展示, 这时命令行nodemon server 启动服务器,如果你没有安装nodemon, 可以npm install -g nodemon 安装它。启动成功后,在浏览器中输入 http://localhost:3000, 可以看到页面如下, 展示正确

  现在我们来动态获取充值面值。当点击确定按钮时, 我们首先要根据手机号得到省和市,所以写一个方法来发送请求获取省和市,方法命名为getLocation, 接受一个参数phoneNum , 后台接口名为phoneLocation,当获取到城市位置以后,我们再发送请求获取充值面值,所以还要再写一个方法getFaceList, 它接受两个参数, province 和city, 后台接口为faceList,在methods 下面添加这两个方法getLocation, getFaceList

复制代码

        methods: {
            //获取到城市信息
            getLocation(phoneNum) {
               return axios.post('phoneLocation', {
                    phoneNum
                })
            },
            // 获取面值
            getFaceList(province, city) {
                return axios.post('/faceList', {
                    province,
                    city
                })
            },
            // 点击确定按钮时,获取面值列表
            getFaceResult () {
               
            }
        }

复制代码

  现在再把两个后台接口写好,为了演示,写的非常简单,没有进行任何的验证,只是返回前端所需要的数据。Express 写这种简单的接口还是非常方便的,在app.use 和app.listen 之间添加如下代码

复制代码

// 电话号码返回省和市,为了模拟延迟,使用了setTimeout
app.post('/phoneLocation', (req, res) => {
    setTimeout(() => {
        res.json({
            success: true,
            obj: {
                province: '广东',
                city: '深圳'
            }
        })
    }, 1000);
})

// 返回面值列表
app.post('/faceList', (req, res) => {
    setTimeout(() => {
        res.json(
            {
                success: true,
                obj:['20元', '30元', '50元']
            }
            
        )
    }, 1000);
})

复制代码

  最后是前端页面中的click 事件的getFaceResult, 由于axios 返回的是promise 对象,我们使用then 的链式写法,先调用getLocation方法,在其then方法中获取省和市,然后再在里面调用getFaceList,再在getFaceList 的then方法获取面值列表,

复制代码

            // 点击确定按钮时,获取面值列表
            getFaceResult () {
                this.getLocation(this.phoneNum)
                    .then(res => {
                        if (res.status === 200 && res.data.success) {
                            let province = res.data.obj.province;
                            let city = res.data.obj.city;

                            this.getFaceList(province, city)
                                .then(res => {
                                    if(res.status === 200 && res.data.success) {
                                        this.faceList = res.data.obj
                                    }
                                })
                        }
                    })
                    .catch(err => {
                        console.log(err)
                    })
            }

复制代码

  现在点击确定按钮,可以看到页面中输出了 从后台返回的面值列表。这时你看到了then 的链式写法,有一点回调地域的感觉。现在我们在有async/ await 来改造一下。

首先把 getFaceResult 转化成一个async 函数,就是在其前面加async, 因为它的调用方法和普通函数的调用方法是一致,所以没有什么问题。然后就把 getLocation 和

getFaceList 放到await 后面,等待执行, getFaceResult  函数修改如下

复制代码

            // 点击确定按钮时,获取面值列表
            async getFaceResult () {
                let location = await this.getLocation(this.phoneNum);
                if (location.data.success) {
                    let province = location.data.obj.province;
                    let city = location.data.obj.city;
                    let result = await this.getFaceList(province, city);
                    if (result.data.success) {
                        this.faceList = result.data.obj;
                    }
                }
            }

复制代码

  现在代码的书写方式,就像写同步代码一样,没有回调的感觉,非常舒服。

  现在就还差一点需要说明,那就是怎么处理异常,如果请求发生异常,怎么处理? 它用的是try/catch 来捕获异常,把await 放到 try 中进行执行,如有异常,就使用catch 进行处理。

复制代码

            async getFaceResult () {
                try {
                    let location = await this.getLocation(this.phoneNum);
                    if (location.data.success) {
                        let province = location.data.obj.province;
                        let city = location.data.obj.city;
                        let result = await this.getFaceList(province, city);
                        if (result.data.success) {
                            this.faceList = result.data.obj;
                        }
                    }
                } catch(err) {
                    console.log(err);
                }
            }

复制代码

  现在把服务器停掉,可以看到控制台中输出net Erorr,整个程序正常运行。

2015-10-16 18:05:03 zdzqyj0312 阅读数 690

概述

该单例模仿的是nodejsasync模块,只抽取了parallelmap这两个api,今后可能会根据需求继续添加。该单例的目的是简化异步调用的代码书写。

cc.async.parallel

异步执行多个任务。在H5中多为HTTP请求。

参数:

name type remark
tasks Array 必填,数组里的每一项都是一个function
option Object/Function 选填,该参数在nodejs的async模块中是没有的,这里作为拓展参数。当为方法时作为触发器使用,为Object的时候结构为:{cb:function, cbTarget:object/null, trigger:function, triggerTarget:object/null}
cb Function 选填,所有异步调用结束后的总回调函数

用法1:

cc.async.parallel([
    function(cb){
        cb(null, "a");//此处代替异步调用方法
    },
    function(cb){
        cb(null, "B");//此处代替异步调用方法
    }
], function(err, results){
    if(err) throw err;//error
    console.log(results);//["a", "B"]
});

用法2:

cc.async.parallel([
    function(cb){
        cb(null, "a");//此处代替异步调用方法
    },
    function(cb){
        cb(null, "B");//此处代替异步调用方法
    }
], function(item, count, totalNum){
    console.log(item, count, totalNum);
}, function(err, results){
    if(err) return console.error(err);
    console.log(results);//["a", "B"]
});

用法3:

var tempObj = {
    name : "TempObj",
    trigger : function(item, count, totalNum){
        console.log(this.name);//TempObj
        console.log(item, count, totalNum);
    },
    cb : function(err, results){
        if(err) return console.error(err);
        console.log(this.name);//TempObj
        console.log(results);//["a", "B"]
    }
};
var option = {
    trigger : tempObj.trigger,
    triggerTarget : tempObj,
    cb : tempObj.cb,
    cbTarget : tempObj
};
cc.async.parallel([
    function(cb){
        cb(null, "a");//此处代替异步调用方法
    },
    function(cb){
        cb(null, "B");//此处代替异步调用方法
    }
], option);

cc.async.map

参数:

name type remark
tasks Array/Object 必填,数组里的每一项都是一个function
option Object/Function 必填,该参数与nodejs的async模块有所不同,这里作为拓展参数。当为方法时作为iterator使用,为Object的时候结构为:{cb:function, cbTarget:object/null, iterator:function, iteratorTarget:object/null}
cb Function 选填,所有异步调用结束后的总回调函数。当tasks为数组时,返回的results为数组,如果tasks是object,则返回的results也为一个object,并且每个key对应其返回结果。

用法1:

cc.async.map(["a", "B"], function(item, index, cb){
    console.log(this.name);//TempObj
    cb(null, index + ":" + item);//此处代替异步调用方法
}, function(err, results){
    if(err) return console.error(err);
    console.log(results);//[ '0:a', '1:B' ]
});

用法2:

cc.async.map({a:"a", b:"B"}, function(item, key, cb){
    cb(null, key + ":" + item);//此处代替异步调用方法
}, function(err, results){
    if(err) return console.error(err);
    console.log(results);//{ a: 'a:a', b: 'b:B' }
});

用法3:

var tempObj = {
    name : "TempObj",
    iterator : function(item, key, cb){
        cb(null, key + ":" + item);//此处代替异步调用方法
    },
    cb : function(err, results){
        if(err) return console.error(err);
        console.log(this.name);//TempObj
        console.log(results);//{ a: 'a:a', b: 'b:B' }
    }
};
var option = {
    iterator : tempObj.iterator,
    iteratorTarget : tempObj,
    cb : tempObj.cb,
    cbTarget : tempObj
};
转载自:cocos2dx开发网
2018-08-12 23:24:42 rerevc 阅读数 3405
const {ccclass, property} = cc._decorator;

@ccclass
export default class Helloworld extends cc.Component {

    @property(cc.Label)
    label: cc.Label = null;

    @property
    text: string = 'hello';

    async start () {
        // init logic
        this.label.string = this.text;
        this.label.string = await this.testAsync();
        
    }

    async testAsync() : Promise<string> {
        return new Promise<string>((resolve, reject)=>{
            setTimeout(() => {
                resolve("liang miao yi hou ");
            }, 2000);
        });
    }
}

 

2018-11-08 11:27:54 zunguitiancheng 阅读数 0

https://www.cnblogs.com/SamWeb/p/8417940.html

 

昨天看了一篇vue的教程,作者用async/ await来发送异步请求,从服务端获取数据,代码很简洁,同时async/await 已经被标准化,是时候学习一下了。

  先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。 写一个async 函数

async function timeout() {
  return 'hello world';
}

   语法很简单,就是在函数前面加上async 关键字,来表示它是异步的,那怎么调用呢?async 函数也是函数,平时我们怎么使用函数就怎么使用它,直接加括号调用就可以了,为了表示它没有阻塞它后面代码的执行,我们在async 函数调用之后加一句console.log;

async function timeout() {
    return 'hello world'
}
timeout();
console.log('虽然在后面,但是我先执行');

  打开浏览器控制台,我们看到了

  async 函数 timeout  调用了,但是没有任何输出,它不是应该返回 'hello world',  先不要着急, 看一看timeout()执行返回了什么? 把上面的 timeout() 语句改为console.log(timeout())

async function timeout() {
    return 'hello world'
}
console.log(timeout());
console.log('虽然在后面,但是我先执行');

  继续看控制台

  原来async 函数返回的是一个promise 对象,如果要获取到promise 返回值,我们应该用then 方法, 继续修改代码

按 Ctrl+C 复制代码

 

按 Ctrl+C 复制代码

  看控制台

  我们获取到了"hello world',  同时timeout 的执行也没有阻塞后面代码的执行,和 我们刚才说的一致。

  这时,你可能注意到控制台中的Promise 有一个resolved,这是async 函数内部的实现原理。如果async 函数中有返回一个值 ,当调用该函数时,内部会调用Promise.solve() 方法把它转化成一个promise 对象作为返回,但如果timeout 函数内部抛出错误呢? 那么就会调用Promise.reject() 返回一个promise 对象, 这时修改一下timeout 函数

复制代码

async function timeout(flag) {
    if (flag) {
        return 'hello world'
    } else {
        throw 'my god, failure'
    }
}
console.log(timeout(true))  // 调用Promise.resolve() 返回promise 对象。
console.log(timeout(false)); // 调用Promise.reject() 返回promise 对象。

复制代码

  控制台如下:

  如果函数内部抛出错误, promise 对象有一个catch 方法进行捕获。

timeout(false).catch(err => {
    console.log(err)
})

  async 关键字差不多了,我们再来考虑await 关键字,await是等待的意思,那么它等待什么呢,它后面跟着什么呢?其实它后面可以放任何表达式,不过我们更多的是放一个返回promise 对象的表达式。注意await 关键字只能放到async 函数里面

  现在写一个函数,让它返回promise 对象,该函数的作用是2s 之后让数值乘以2

复制代码

// 2s 之后返回双倍的值
function doubleAfter2seconds(num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2 * num)
        }, 2000);
    } )
}

复制代码

  现在再写一个async 函数,从而可以使用await 关键字, await 后面放置的就是返回promise对象的一个表达式,所以它后面可以写上 doubleAfter2seconds 函数的调用

async function testResult() {
    let result = await doubleAfter2seconds(30);
    console.log(result);
}

  现在调用testResult 函数

testResult();

  打开控制台,2s 之后,输出了60. 

  现在我们看看代码的执行过程,调用testResult 函数,它里面遇到了await, await 表示等一下,代码就暂停到这里,不再向下执行了,它等什么呢?等后面的promise对象执行完毕,然后拿到promise resolve 的值并进行返回,返回值拿到之后,它继续向下执行。具体到 我们的代码, 遇到await 之后,代码就暂停执行了, 等待doubleAfter2seconds(30) 执行完毕,doubleAfter2seconds(30) 返回的promise 开始执行,2秒 之后,promise resolve 了, 并返回了值为60, 这时await 才拿到返回值60, 然后赋值给result, 暂停结束,代码才开始继续执行,执行 console.log语句。

  就这一个函数,我们可能看不出async/await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?

复制代码

async function testResult() {
    let first = await doubleAfter2seconds(30);
    let second = await doubleAfter2seconds(50);
    let third = await doubleAfter2seconds(30);
    console.log(first + second + third);
}

复制代码

  6秒后,控制台输出220, 我们可以看到,写异步代码就像写同步代码一样了,再也没有回调地域了。

  再写一个真实的例子,我原来做过一个小功能,话费充值,当用户输入电话号码后,先查找这个电话号码所在的省和市,然后再根据省和市,找到可能充值的面值,进行展示。

为了模拟一下后端接口,我们新建一个node 项目。 新建一个文件夹 async, 然后npm init -y 新建package.json文件,npm install express --save 安装后端依赖,再新建server.js 文件作为服务端代码, public文件夹作为静态文件的放置位置, 在public 文件夹里面放index.html 文件, 整个目录如下

 

  server.js 文件如下,建立最简单的web 服务器

复制代码

const express = require('express');
const app = express();// express.static 提供静态文件,就是html, css, js 文件
app.use(express.static('public'));

app.listen(3000, () => {
    console.log('server start');
})

复制代码

  再写index.html 文件,我在这里用了vue构建页面,用axios 发送ajax请求, 为了简单,用cdn 引入它们。 html部分很简单,一个输入框,让用户输入手机号,一个充值金额的展示区域, js部分,按照vue 的要求搭建了模版

复制代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Async/await</title>
    <!-- CDN 引入vue 和 axios -->
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
    <div id="app">

        <!-- 输入框区域 -->
        <div style="height:50px">
            <input type="text" placeholder="请输入电话号码" v-model="phoneNum">
            <button @click="getFaceResult">确定</button>
        </div>

        <!-- 充值面值 显示区域 -->
        <div>
            充值面值:
            <span v-for="item in faceList" :key='item'>
                {{item}}
            </span>
        </div>
    </div>

    <!-- js 代码区域 -->
    <script>
        new Vue({
            el: '#app',
            data: {
                phoneNum: '12345',
                faceList: ["20元", "30元", "50元"]
            },
            methods: {
                getFaceResult() {

                }
            }
        })
    </script>
</body>
</html>

复制代码

  为了得到用户输入的手机号,给input 输入框添加v-model指令,绑定phoneNum变量。展示区域则是 绑定到faceList 数组,v-for 指令进行展示, 这时命令行nodemon server 启动服务器,如果你没有安装nodemon, 可以npm install -g nodemon 安装它。启动成功后,在浏览器中输入 http://localhost:3000, 可以看到页面如下, 展示正确

  现在我们来动态获取充值面值。当点击确定按钮时, 我们首先要根据手机号得到省和市,所以写一个方法来发送请求获取省和市,方法命名为getLocation, 接受一个参数phoneNum , 后台接口名为phoneLocation,当获取到城市位置以后,我们再发送请求获取充值面值,所以还要再写一个方法getFaceList, 它接受两个参数, province 和city, 后台接口为faceList,在methods 下面添加这两个方法getLocation, getFaceList

复制代码

        methods: {
            //获取到城市信息
            getLocation(phoneNum) {
               return axios.post('phoneLocation', {
                    phoneNum
                })
            },
            // 获取面值
            getFaceList(province, city) {
                return axios.post('/faceList', {
                    province,
                    city
                })
            },
            // 点击确定按钮时,获取面值列表
            getFaceResult () {
               
            }
        }

复制代码

  现在再把两个后台接口写好,为了演示,写的非常简单,没有进行任何的验证,只是返回前端所需要的数据。Express 写这种简单的接口还是非常方便的,在app.use 和app.listen 之间添加如下代码

复制代码

// 电话号码返回省和市,为了模拟延迟,使用了setTimeout
app.post('/phoneLocation', (req, res) => {
    setTimeout(() => {
        res.json({
            success: true,
            obj: {
                province: '广东',
                city: '深圳'
            }
        })
    }, 1000);
})

// 返回面值列表
app.post('/faceList', (req, res) => {
    setTimeout(() => {
        res.json(
            {
                success: true,
                obj:['20元', '30元', '50元']
            }
            
        )
    }, 1000);
})

复制代码

  最后是前端页面中的click 事件的getFaceResult, 由于axios 返回的是promise 对象,我们使用then 的链式写法,先调用getLocation方法,在其then方法中获取省和市,然后再在里面调用getFaceList,再在getFaceList 的then方法获取面值列表,

复制代码

            // 点击确定按钮时,获取面值列表
            getFaceResult () {
                this.getLocation(this.phoneNum)
                    .then(res => {
                        if (res.status === 200 && res.data.success) {
                            let province = res.data.obj.province;
                            let city = res.data.obj.city;

                            this.getFaceList(province, city)
                                .then(res => {
                                    if(res.status === 200 && res.data.success) {
                                        this.faceList = res.data.obj
                                    }
                                })
                        }
                    })
                    .catch(err => {
                        console.log(err)
                    })
            }

复制代码

  现在点击确定按钮,可以看到页面中输出了 从后台返回的面值列表。这时你看到了then 的链式写法,有一点回调地域的感觉。现在我们在有async/ await 来改造一下。

首先把 getFaceResult 转化成一个async 函数,就是在其前面加async, 因为它的调用方法和普通函数的调用方法是一致,所以没有什么问题。然后就把 getLocation 和

getFaceList 放到await 后面,等待执行, getFaceResult  函数修改如下

复制代码

            // 点击确定按钮时,获取面值列表
            async getFaceResult () {
                let location = await this.getLocation(this.phoneNum);
                if (location.data.success) {
                    let province = location.data.obj.province;
                    let city = location.data.obj.city;
                    let result = await this.getFaceList(province, city);
                    if (result.data.success) {
                        this.faceList = result.data.obj;
                    }
                }
            }

复制代码

  现在代码的书写方式,就像写同步代码一样,没有回调的感觉,非常舒服。

  现在就还差一点需要说明,那就是怎么处理异常,如果请求发生异常,怎么处理? 它用的是try/catch 来捕获异常,把await 放到 try 中进行执行,如有异常,就使用catch 进行处理。

复制代码

            async getFaceResult () {
                try {
                    let location = await this.getLocation(this.phoneNum);
                    if (location.data.success) {
                        let province = location.data.obj.province;
                        let city = location.data.obj.city;
                        let result = await this.getFaceList(province, city);
                        if (result.data.success) {
                            this.faceList = result.data.obj;
                        }
                    }
                } catch(err) {
                    console.log(err);
                }
            }

复制代码

  现在把服务器停掉,可以看到控制台中输出net Erorr,整个程序正常运行。