Promise和async/await

本文深入探讨了JavaScript中Promise的原理与应用,包括创建、链式调用、错误处理及Promise.all和Promise.race方法。同时,文章详细介绍了async/await语法,展示了如何简化异步编程,提高代码的可读性和维护性。

Promise

什么是Promise
Promise的出现是为了更好地解决JavaScript中异步编程的问题,传统的异步编程最大的特点就是地狱般的回调嵌套,一旦嵌套次数过多,就很容易使我们的代码难以理解和维护。而Promise则可以让我们通过链式调用的方法去解决回调嵌套的问题,使我们的代码更容易理解和维护,而且Promise还增加了许多有用的特性,让我们处理异步编程得心应手。

如何创建Promise
ES6给我们提供了一个原生的构造函数Promise,我们先来创建一个promise,下面是一个简单的示例:

// 我们先使用ES5的语法
var promise = new Promise(function(resolve, reject) {
    var flag = Math.random();
    setTimeout(function() {
        if(flag) {
            resolve('success');
        }
        else {
            reject('fail');
        }
    }, 1000);
});

promise.then(function(result) {
    console.log(result);
}, function(err) {
    console.log(err);
}); 

下面来解释一下上面的代码:

  1. 因为Promise是一个构造函数,所以我们使用了new操作符来创建promise。
  2. 构造函数Promise的参数是一个函数(暂时叫它func),这个函数(func)有两个参数resolve和reject,它们分别是两个函数,这两个函数的作用就是将promise的状态从pending(等待)转换为resolved(已解决)或者从pending(等待)转换为rejected(已失败)。
  3. 创建后的promise有一些方法,then和catch。

如果我们使用一些ES6的语法的话,我们上面的代码会更加简洁:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        Math.random() > 0.5 ? resolve('success') : reject('fail');
    }, 1000)
});

p.then((result) => {
    console.log(result);
}, (err) => {
    console.log(err);
});

其实还是是很好理解的,Promise函数体的内部包裹着一个异步的请求或者操作或者函数;然后我们可以在这个异步的操作完成的时候使用resolve函数将我们获得的结果传递出去,或者使用reject函数将错误的消息传递出去。

Promise对象的一些方法
Promise对象可以通过使用then方法将上一步返回的结果获取过来(不管是resolved还是rejected),可以通过使用catch方法捕获Promise对象在使用catch之前的异常。
首先来说一下then方法的使用:

let p = new Promise((resolve, reject) => {
   let flag = Math.random() > 0.5 ? true : false;
   if(flag) {
       console.log('使用resolve将promise状态从pending变为resolved');
       resolve('success');
   }
   else {
       console.log('使用reject将promise状态从pending变为rejected');
       reject('fail');
   }
});

p.then((result) => {
    console.log('接受resolved的结果');
    console.log(result);
}, (err) => {
    console.log('捕获错误的结果');
    console.log(err);
});

我们可以看到,then方法可以接受两个函数作为参数,第一个函数是用来处理resolve的结果,第二个是可选的,用来处理reject的结果。也就是说,我们在创建p这个Promise对象的时候,通过函数resolve传递出去的结果可以被p的第一个then方法中的第一个函数捕获然后作为它的参数。通过函数reject传递出去的结果可以被p的第一个then方法中的第二个函数捕获然后作为它的参数。
当然我们还可以在每一个then方法中创建新的Promise,然后将这个Promise对象返回,之后我们就可以在后面的then方法中继续对这个对象进行操作。下面是一个简单的例子:

let p1 = new Promise((resolve, reject) => {
    let flag = Math.random() > 0.5 ? true : false;
    resolve();
});
// 使用then方法进行链式的调用
p1.then(() => {
    return 1;
}).then((result) => {
    console.log(result);
    return 'hello'
}).then((result) => {
    console.log(result);
});

//  在then方法内部可以再次使用异步的操作
p1.then(() => {
    console.log('******');
    let p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(123);
        }, 1000);
    });
    return p1;
}).then((result) => {
    console.log(result);
});

从上面的代码中我们可以看到,一旦创建一个Promise对象之后,我们就可以使用then方法来进行链式的调用,而且我们可以把每一次的结果都返还给下一个then方法,然后在下一个then方法中对这个值进行处理。每一个then方法中都可以再次新创建一个Promise对象,然后返还给下一个then方法处理。
Promise还有另一个方法catch,这个方法其实是then方法的一种特例,这个特例就是:

.then(null, rejection)

相当于我们不使用then方法的第一个函数,只是用第二个函数;catch函数比较简单,就是用来捕获之前的then方法里面的异常,我们可以简单的来看一个例子:

let p = new Promise((resolve, reject) => {
    resolve();
});
p.then(() => {
    console.log('progress...');
}).then(() => {
    throw new Error('fail');
}).catch((err) => {
    console.log(err);
});

上面代码的输出结果如下:

progress...
VM141:9 Error: fail()

Promise.all方法用来包装许多个Promise实例,然后组成了一个新的Promise对象,新的Promise对象的状态由前面几个被包裹的Promise对象的状态决定,如果前面的Promise都被resolve了,那么新的Promise的状态也是resolve的;只要有一个Promise被reject了,那么组成的新的Promise的状态也是reject的。
可以看下面一个例子:

let arr = [1, 2, 3].map(
    (value) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(value);
            }, value * 1000);
        });
    }
);

let promises = Promise.all(arr)
.then((result) => {
    console.log(result);
}).catch((err) => {
    console.log(err);
});

上面的代码的输出结果如下:

[ 1, 2, 3 ] 

Promise.race方法和上面的Promise.all有点类似,都是包装许多的Promise对象,然后组成了一个新的Promise对象,但是使用Promise.race的含义是:只要包裹的的Promise对象中有一个的状态发生了改变,那么组成的这个新的Promise对象的状态就是上面那个率先改变的Promise实例的状态。

let arr = [1, 2, 3].map(
    (value) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(value);
            }, value * 1000);
        });
    }
);
let promises = Promise.race(arr)
    .then((result) => {
        console.log(result);
    }).catch((err) => {
        console.log(err);
    });

上面程序的输出结果如下:

1 // 是最先改变状态的那个Promise实例resolve的值

Promise.resolve方法主要是将一个值转变为一个Promise对象,然后使它具有Promise的一些方法和特性,为了满足我们一些特殊情况下的要求。

let arr = [null, 0, 'hello',
    { then: function() { console.log(' a thenable obj')}}
];

arr.map((value) => {
        return Promise.resolve(value);
    });

上面的输出结果如下:

 a thenable obj // Promise.resolve方法会将具有then方法的对象转换为一个Promise对象,然后就立即执行then方法。

Promise.reject方法和Promise.resolve方法一样,只不过通过Promise.reject方法产生的Promise对象的状态是rejected的,下面是一个示例:

let p = Promise.reject('fail');
p.catch((err) => {
    console.log(err);
}); // fail

async/await

async 是“异步”的简写,async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成,await 只能出现在 async 函数中。
async 的作用
async 函数负责返回一个 Promise 对象,如果在async函数中 return 一个直接量,async 会把这个直接量通过Promise.resolve() 封装成 Promise 对象。
await 在等待什么
一般我们都用await去等带一个async函数完成,不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值,所以,await后面实际可以接收普通函数调用或者直接量。如果await等到的不是一个promise对象,那跟着的表达式的运算结果就是它等到的东西;
如果是一个promise对象,await会阻塞后面的代码,等promise对象resolve,得到resolve的值作为await表达式的运算结果。

Async Await使用场景
如上面的例子,当需要用到promise链式调用的时候,就体现出Async Await的优势。
假设一个业务需要分步完成,每个步骤都是异步的,而且依赖上一步的执行结果,甚至依赖之前每一步的结果,就可以使用Async Await来完成。

function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}
function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}
function step2(m, n) {
    console.log(`step2 with ${m} and ${n}`);
    return takeLongTime(m + n);
}
function step3(k, m, n) {
    console.log(`step3 with ${k}, ${m} and ${n}`);
    return takeLongTime(k + m + n);
}

async function doIt() {
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
}

doIt();

如果用promise来实现

function doIt() {
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
        });
}

doIt();

可见用promise,参数传递非常麻烦。

注意
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中,或者await后的Promise添加catch回调。

await read('1.txt','utf8').catch(function(err){
    console.log(err);
})

await 只能出现在 async 函数中,如果用在普通函数,就会报错。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

上面代码会报错,因为 await 用在普通函数之中了。

总结
使用 async / await, 搭配 promise, 可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值