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

你可能感兴趣的:(JS 异步编程)