JS 异步编程

Promise

ECMAscript 6 原生提供了 Promise 对象。
Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。
Promise 对象有以下两个特点:
1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:

  • pending: 初始状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

image.png

Promise的使用

1.then基本使用

场景:请求网络接口,对成功和失败结果做不同处理

// 基本使用
new Promise((resolve, reject) => {
    console.log('task start')
    // 模拟请求网络数据
    setTimeout(() => {
        resolve('{"name": "james"}')      // 成功
        // reject(new Error('network error!')) // 失败
    }, 1000);
}).then(value => {
    // 处理成功的回调
    console.log('success:' + value)
}, error => {
    // 处理失败的回调
    console.log('fail:' + error)
})

成功:

task start
success:{"name": "james"}

失败:

task start
fail:Error: network error!

2.then的多次调用

场景:例如一个页面需要请求一个接口,请求成功后需要:
1).将数据展示到页面
2).数据上报
那么可以这样实现:

// 2.then的多次调用
let p1 = new Promise((resolve, reject) => {
    console.log('task start')
    // 模拟请求网络数据
    setTimeout(() => {
        //resolve('{"name": "james"}')      // 成功
        reject(new Error('network error!')) // 失败
    }, 1000);
})
// 1.显示到页面
p1.then(value => {
    console.log('to show success:' + value)
}, error => {
    console.log('to show fail1:' + error)
})

// 2.数据上报
p1.then(value => {
    console.log('to report success2:' + value)
}, error => {
    console.log('to report fail2:' + error)
})

成功:

task start
to show success:{"name": "james"}
to report success2:{"name": "james"}

失败:

task start
to show fail1:Error: network error!
to report fail2:Error: network error!

3.then的链式调用

场景:实际开发中,我们肯定需要对网络数据做处理,例如解密,json/xml解析,过滤等等操作,demo中是以获取json数据中name的字段,并将首字母大写

new Promise((resolve, reject) => {
    console.log('task start')
    // 模拟请求网络数据
    setTimeout(() => {
        resolve('{"name": "james"}')      // 成功
        reject(new Error('network error!')) // 失败
    }, 1000);
}).then(rawData => {
    return JSON.parse(rawData)
}).then(jsonObj => {
    return jsonObj.name
}).then(oriName => {
    return oriName.replace(oriName[0], oriName[0].toUpperCase())
}).then(name => {
    console.log(name)
})

输出:

task start
James

4.catch的使用

4.1捕获reject失败
new Promise((resolve, reject) => {
    console.log('task start')
    // 模拟请求网络数据
    setTimeout(() => {
        //resolve('{"name": "james"}')      // 成功
        reject(new Error('network error!')) // 失败
    }, 1000);
}).then(value => {
    // 处理成功的回调
    console.log('success:' + value)
}).catch(error => {
    // 处理失败的回调
    console.log('fail:' + error)
})

// 输出:
task start
fail:Error: network error!
4.2捕获执行器异常
new Promise((resolve, reject) => {
    console.log('task start')
    throw new Error('executor error')
    // 模拟请求网络数据
    setTimeout(() => {
        //resolve('{"name": "james"}')      // 成功
        reject(new Error('network error!')) // 失败
    }, 1000);
}).then(value => {
    // 处理成功的回调
    console.log('success:' + value)
}).catch(error => {
    // 处理失败的回调
    console.log('fail:' + error)
})

// 输出:
task start
fail:Error: executor error
4.3捕获then异常
new Promise((resolve, reject) => {
    console.log('task start')
    // 模拟请求网络数据
    setTimeout(() => {
        resolve('{"name": "james"}')      // 成功
        //reject(new Error('network error!')) // 失败
    }, 1000);
}).then(value => {
    // 处理成功的回调
    throw new Error('then error')
    console.log('success:' + value)
}).catch(error => {
    // 处理失败的回调
    console.log('fail:' + error)
})

// 输出:
task start
fail:Error: then error

5.finally的使用

不管promise最终的状态如何,finally传递的回调始终会执行

new Promise((resolve, reject) => {
    console.log('task start')
    //throw new Error('executor error')
    // 模拟请求网络数据
    setTimeout(() => {
        resolve('{"name": "james"}')      // 成功
        //reject(new Error('network error!')) // 失败
    }, 1000);
}).then(value => {
    // 处理成功的回调
    //throw new Error('then error')
    console.log('success:' + value)
}).catch(error => {
    // 处理失败的回调
    console.log('fail:' + error)
}).finally(() => {
    console.log('task end')
})

正常输出:

task start
success:{"name": "james"}
task end

reject失败输出:

task start
fail:Error: network error!
task end

执行器异常输出:

task start
fail:Error: executor error
task end

then异常输出:

task start
fail:Error: then error
task end

6.Promise.all的使用

all接收一个数组,数组里的元素可以是普通值,也可以是promise对象,普通对象直接返回,promise对象则等待其最终状态确定后返回。
数组里的promise都成功的情况,最终结果就是成功

function timeConsole(msg) {
    let date = new Date()
    console.log(date.toTimeString() + "->" + msg)
}

delayResolve = function(delay, message) {
    return new Promise(function (resolve, reject) {
        timeConsole(message + ' start')
        setTimeout(function () {
            timeConsole(message + ' success');
            resolve(message);
        }, delay);
    });
},

delayReject = function(delay, message) {
    return new Promise(function (resolve, reject) {
        timeConsole(message + ' start')
        setTimeout(function () {
            timeConsole(message + ' fail');
            reject(message);
        }, delay);
    });
}

Promise.all(['task1', delayResolve(2000, "task2"), delayResolve(1000, "task3"), 'task4'])
.then(function(v) {
    timeConsole('all end:' + v)
}).catch(function(e){
    timeConsole("catched:" + e);
}).finally(function() {
    timeConsole("finally");
});

输出:

14:28:36 GMT+0800 (GMT+08:00)->task2 start
14:28:36 GMT+0800 (GMT+08:00)->task3 start
14:28:37 GMT+0800 (GMT+08:00)->task3 success
14:28:38 GMT+0800 (GMT+08:00)->task2 success
14:28:38 GMT+0800 (GMT+08:00)->all end:task1,task2,task3,task4
14:28:38 GMT+0800 (GMT+08:00)->finally

数组中的promise只要有一个失败,则最终结果就是失败

Promise.all(['task1', delayReject(2000, "task2"), delayResolve(1000, "task3"), 'task4'])
.then(function(v) {
    timeConsole('all end:' + v)
}).catch(function(e){
    timeConsole("catched:" + e);
}).finally(function() {
    timeConsole("finally");
});
14:36:21 GMT+0800 (GMT+08:00)->task2 start
14:36:21 GMT+0800 (GMT+08:00)->task3 start
14:36:22 GMT+0800 (GMT+08:00)->task3 success
14:36:23 GMT+0800 (GMT+08:00)->task2 fail
14:36:23 GMT+0800 (GMT+08:00)->catched:task2 error
14:36:23 GMT+0800 (GMT+08:00)->finally

7.Promise.race的使用

race也接收一个数组,race最终的结果取决于最先结束的promise的结果

Promise.race([delayReject(2000, "task2"), delayResolve(1000, "task3")])
.then(function(v) {
    timeConsole('all end:' + v)
}).catch(function(e){
    timeConsole("catched:" + e);
}).finally(function() {
    timeConsole("finally");
});

成功

14:48:19 GMT+0800 (GMT+08:00)->task2 start
14:48:19 GMT+0800 (GMT+08:00)->task3 start
14:48:20 GMT+0800 (GMT+08:00)->task3 success
14:48:20 GMT+0800 (GMT+08:00)->all end:task3
14:48:20 GMT+0800 (GMT+08:00)->finally
14:48:21 GMT+0800 (GMT+08:00)->task2 fail

失败

14:51:56 GMT+0800 (GMT+08:00)->task2 start
14:51:56 GMT+0800 (GMT+08:00)->task3 start
14:51:57 GMT+0800 (GMT+08:00)->task2 fail
14:51:57 GMT+0800 (GMT+08:00)->catched:task2 error
14:51:57 GMT+0800 (GMT+08:00)->finally
14:51:58 GMT+0800 (GMT+08:00)->task3 success

Generator

ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
Generator 有两个区分于普通函数的部分:

  • 一是在 function 后面,函数名之前有个*
  • 函数内部有yield表达式。

其中 * 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态。

function timeConsole(msg) {
    let date = new Date()
    console.log(date.toTimeString() + "->" + msg)
}

function * gen() {
    timeConsole('before 1')
    yield '1'
    timeConsole('after 1')

    timeConsole('before 2')
    yield '2'
    timeConsole('after 2')

    timeConsole('before 3')
    yield '3'
    timeConsole('after 3')
}

let g = gen()

setTimeout(() => {
    timeConsole(g.next().value)
}, 0);

setTimeout(() => {
    timeConsole(g.next().value)
}, 1000);

setTimeout(() => {
    timeConsole(g.next().value)
}, 2000);

setTimeout(() => {
    timeConsole(g.next().value)
}, 3000);

输出

16:28:25 GMT+0800 (GMT+08:00)->before 1
16:28:25 GMT+0800 (GMT+08:00)->1
16:28:26 GMT+0800 (GMT+08:00)->after 1
16:28:26 GMT+0800 (GMT+08:00)->before 2
16:28:26 GMT+0800 (GMT+08:00)->2
16:28:27 GMT+0800 (GMT+08:00)->after 2
16:28:27 GMT+0800 (GMT+08:00)->before 3
16:28:27 GMT+0800 (GMT+08:00)->3
16:28:28 GMT+0800 (GMT+08:00)->after 3
16:28:28 GMT+0800 (GMT+08:00)->undefined

使用next()传入参数,yield接收参数

function * gen() {
    timeConsole('before 1')
    let r1 = yield '1'
    timeConsole(r1)
    timeConsole('before 2')
    let r2 = yield '2'
    timeConsole(r2)
    timeConsole('before 3')
    let r3 = yield '3'
    timeConsole(r3)
}

let g = gen()

setTimeout(() => {
    timeConsole(g.next('start').value)
}, 0);

setTimeout(() => {
    timeConsole(g.next('after 1').value)
}, 1000);

setTimeout(() => {
    timeConsole(g.next('after 2').value)
}, 2000);

setTimeout(() => {
    timeConsole(g.next('after 3').value)
}, 3000);

输出

16:34:15 GMT+0800 (GMT+08:00)->before 1
16:34:15 GMT+0800 (GMT+08:00)->1
16:34:16 GMT+0800 (GMT+08:00)->after 1
16:34:16 GMT+0800 (GMT+08:00)->before 2
16:34:16 GMT+0800 (GMT+08:00)->2
16:34:17 GMT+0800 (GMT+08:00)->after 2
16:34:17 GMT+0800 (GMT+08:00)->before 3
16:34:17 GMT+0800 (GMT+08:00)->3
16:34:18 GMT+0800 (GMT+08:00)->after 3
16:34:18 GMT+0800 (GMT+08:00)->undefined

async/await

async/await 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的,使用async/await在处理异步操作时代码可读性会更好

async

async 函数返回一个 Promise 对象,如果不是Promise对象,则自动转换成Promise对象,可以使用 then 方法添加回调函数。

async function myTask(p) {
    return 'task result'
}

let result = myTask()
console.log(result)
result.then(value => console.log(value))

// 输出
// Promise { 'task result' }
// task result

await

await目前只能在异步函数 async function 内部使用,表示等待某表达式的执行结果
await针对所跟不同表达式的处理方式:

  • Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
  • 非 Promise 对象:直接返回对应的值。
function task1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('task1 end')
        }, 3000);
    })
}

function task2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('task2 end')
        }, 2000);
    })
}

function task3() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('task3 end')
        }, 1000);
    })
}

async function myTask() {
    let r0 = await 'task start'
    timeConsole(r0)

    let r1 =  await task1()
    timeConsole(r1)

    let r2 =  await task2()
    timeConsole(r2)

    let r3 =  await task3()
    timeConsole(r3)
}

myTask()

输出

17:12:12 GMT+0800 (GMT+08:00)->task start
17:12:15 GMT+0800 (GMT+08:00)->task1 end
17:12:17 GMT+0800 (GMT+08:00)->task2 end
17:12:18 GMT+0800 (GMT+08:00)->task3 end
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,151评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,465评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,429评论 0 368
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,147评论 1 301
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,149评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,614评论 1 315
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,965评论 3 429
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,950评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,486评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,524评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,640评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,228评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,976评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,407评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,552评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,215评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,714评论 2 366