JS嵌套的异步,如何同步?

login(){
    const that = this
    wx.login({//异步1
      success: (res) => {
        that.getUrl('api/miniapp/index',{code:res.code}).then((res)=>{//异步2
          console.log(res);
        });
      },
      fail:(e)=>{
        console.log(e);
      }
    })
  }
......
async userlogin(){
     await getApp().login();
     console.log('111');
  },

原本以为用了async和await之后,会先打印出登录结果,结果发现先打印的是111
后来发现login函数还存在着两个异步,await只作用于最外层的login函数,并没有作用于内部的异步。我想让他们全部变成同步的咋整?

阅读 2.5k
6 个回答

自定义的login函数需要返回promise才能await
需要等待登录接口后进行操作 可以尝试下面的方法

app.js
  login() {
    wx.login().then(async (res) => {
      let data = await getUrl({ code: res.code });
      this.globalData.token = data.token;
      wx.setStorageSync("TOKEN", data.token);
      if (this.callBack) {
        this.callBack();//回调函数
      }
    });
  },
 onLaunch() {
    const token = wx.getStorageSync("TOKEN");
    if (token) {
      // ...
    } else {
      this.login();
    }
 }
somepage.js
onLoad(options) {
    const token = wx.getStorageSync('TOKEN');
    // 小程序是异步加载的 app.js有异步操作 onload可能会比onLaunch先执行 而获取不到onLaunch的保存的数据
    // 通过回调解决此问题
    if (!token) {
      getApp().callBack = () => {
        // do something
      };
    } else {
       // do something
    }
  },

可以将 wx.login 给 promise 化,这样这可以在外部也使用 await 的形式了,通常这个被我们叫做 promisify 。

本文参与了SegmentFault 思否面试闯关挑战赛,欢迎正在阅读的你也加入。

login没有返回一个Promise对象,自然是没办法通过await得到想要的结果的,改成这样就行了:

login() {
    return new Promise((resolve, reject) => {
        wx.login({ //异步1
            success: (res) => {
                this.getUrl('api/miniapp/index', {
                        code: res.code
                    })
                    .then((res) => { //异步2
                        console.log(res);
                        resolve(res);
                    });
            },
            fail: (e) => {
                console.log(e);
                reject(e);
            }
        })
    });
}

一种方法是在login函数内部也使用async和await关键字,把wx.login和that.getUrl都变成await表达式,这样就可以保证它们按照顺序执行,不会出现先打印111的情况。代码如下:

async login(){
    const that = this
    await wx.login({//异步1
      success: async (res) => {
        await that.getUrl('api/miniapp/index',{code:res.code}).then((res)=>{//异步2
          console.log(res);
        });
      },
      fail:(e)=>{
        console.log(e);
      }
    })
  }

另一种方法是使用Promise.all方法,把wx.login和that.getUrl都封装成Promise对象,然后用Promise.all方法等待它们都完成后再继续执行。代码如下:

login(){
    const that = this
    // 封装wx.login为Promise对象
    let loginPromise = new Promise((resolve, reject) => {
      wx.login({
        success: (res) => {
          resolve(res);
        },
        fail: (e) => {
          reject(e);
        }
      })
    })
    // 封装that.getUrl为Promise对象
    let urlPromise = (code) => {
      return that.getUrl('api/miniapp/index',{code:code});
    }
    // 使用Promise.all方法等待两个异步操作都完成
    Promise.all([loginPromise, loginPromise.then(res => urlPromise(res.code))]).then(values => {
      // values是一个数组,包含了两个异步操作的结果
      console.log(values[1]); // 打印that.getUrl的结果
    }).catch(e => {
      console.log(e); // 打印错误信息
    })
  }

关于两种方法的区别
一般来说,async和await关键字可以让异步操作看起来像同步操作一样,代码更简洁易读,也更容易处理错误。但是,async和await关键字只能在支持ES2017标准的环境中使用,如果要兼容老版本的浏览器或者其他平台,可能需要使用Babel等工具进行转换。而Promise.all方法可以在支持ES2015标准的环境中使用,兼容性更好,而且可以同时执行多个异步操作,提高效率。但是,Promise.all方法需要把异步操作封装成Promise对象,代码可能更复杂一些,而且如果有一个异步操作失败了,就会导致整个Promise.all方法失败。所以,你可以根据你的实际情况,选择适合你的方法。

因为你的写法中的 login() 函数内部直接调用了 promise,这个函数本身是一个同步函数。

所以你的 await getApp().login() 是没有用的,这个语句会立马返回一个 undefined。

判断一个函数是不是异步函数,你可以打印它的返回值,如果是一个 promise,那么就是一个异步函数,否则仍然是同步函数。

既然原代码多数是使用的回调模式,那先按回调的方式来处理这个问题。

异步操作的完成时间不确定,所以不能通过返回来得到结果。而是应该设置回调函数,在有结果的时候,通过把结果送给回调函数来对结果进行处理。

const app = {
    // 模拟了一个 getUrl
    getUrl() {
        return Promise.resolve({ code: 200 });
    },
    login(success, fail) {
        //        ^^^^^^^^^^^^^ 既然下面都是用的回调,应该设计一个回调出口输出最终结果
        // 下面❶处使用了箭头函数,不需要使用箭头函数保存 this
        // const that = this;
        wx.login({//异步1
            success: (res) => {     // ❶
                this.getUrl("api/miniapp/index", { code: res.code })
                    .then((res) => {//异步2
                        console.log(res);
                        success(res);
                    });
            },
            fail: (e) => {
                console.log(e);
                fail(e);
            }
        });
    }
};

但是回调的处理在较逻辑较复杂的情况下,写起来比较费脑,所以一般建议尽可能地封装成 Promise,然后使用 await 来处理。我印象中,Wx 的异步调用都已经实现了 Promise,所以在参数不带 successfail 的情况下 wx.login 应该是返回一个 Promise 对象的。

如果不是也没关系,看这篇:在微信小程序中使用 async/await

使用 Promise 和 await 之后,逻辑会清晰得多,代码也会更简洁。但是要注意对 fail(reject) 情况的处理,在 await 的时候,reject 是以异常的形式来处理的。

const app = {
    // 模拟了一个 getUrl
    getUrl() {
        return Promise.resolve({ code: 200 });
    },
    async login() {
        const loginRes = await wx.login();
        const res = await this.getUrl("api/miniapp/index", { code: loginRes.code });
        console.log("in login", res);
        return res;
    }
};

const r = await app.login();
console.log("final result", r);

如果需要处理错误,在里面不需要专门对错误进行分支处理的情况下,可以直接在最外层套一个 try ... catch,也就是

try {
   const r = await app.login();
   console.log("final result", r);
} catch (err) {
   console.log("error", err);
}

参阅:

推荐问题
宣传栏